徹底明白Python package和模塊

python 是通過module組織代碼的芯勘,每一個module就是一個python文件,但是modules是通過package來組織的腺逛。

如果我們自己寫著玩荷愕,有的時候就是一兩個Python文件在同級目錄下,但是當我們開始嘗試開發(fā)更為復雜的項目的時候棍矛,package這個概念的使用就有助于我們組織我們寫的一個個modules安疗。

module的概念相對簡單,所以不會再多說够委,主要是說一下package荐类。

Python package

package的定義很簡單,在當面目錄下有__init__.py文件的目錄即為一個package茁帽。

但是這會分為兩種情況玉罐,第一種情況是一個空的__init__.py文件,另外一個情況是寫了代碼的__init__.py文件潘拨。不管是空的還是有內容的吊输,這個目錄都會被認為是一個package,這是一個標識。

package的初始化工作

一個package 被導入铁追,不管在什么時候__init__.py的代碼都只會被執(zhí)行一次

>>> import package
hello world
>>> import package
>>> import package

由于 package 被導入時 __init__.py 中的可執(zhí)行代碼會被執(zhí)行季蚂,所以小心在 package 中放置你的代碼,盡可能消除它們產生的副作用琅束,比如把代碼盡可能的進行封裝成函數或類扭屁。

__init__.py內的導入順序

當我嘗試導入

from package import something

import語句會首先檢查something是不是__init__.py的變量,然后檢查是不是subpackage疯搅,再檢查是不是module,最后拋出ImportError

所以檢查順序如下:

  1. __init__.py 文件內變量
  2. 是不是package內的subpackage
  3. 是不是package內的module

看個例子

我們有一個如下結構的package

a.py文件內有一個函數

def bar():
    print("Hello, function 'bar' from module 'a' calling")

b.py文件內有一個函數

def foo():
    print("Hello, function 'foo' from module 'b' calling")

然后我們添加一個空的__init__.py 文件在simple_package里面幔欧。

我們看下,當我們import simple_package的時候到底會發(fā)生什么事情(在simple_package內激活Python shell 或者simple_package的的路徑被包含在pythonsys.path或者在PYTHONPATH的環(huán)境變量中)

>>> import simple_package
>>> 
>>> simple_package
<module 'simple_package' from '/home/bernd/Dropbox (Bodenseo)/websites/python-course.eu/examples/simple_package/__init__.py'>
>>> 
>>> simple_package.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 
>>> simple_package.b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

我們可以看到simple_package已經被成功導入礁蔗,但是a.pyb.py并沒有被導入

當然了雁社,如果你希望使用import simple_package后自動加載a或者b 模塊,這里有兩種辦法霉撵。

第一種就是在__init__.py內導入a或者b模塊洪囤,然后保存再激活python的交互環(huán)境

#__init__.py
import a
import b

當你再次嘗試import simple_package后,就可以使用simple_package.a.bar()來使用模塊a中的bar()函數了瘤缩。

第二辦法就是手動導入,當你想使用模塊a中的bar()函數時伦泥,需要手動導入

import simple_package.a as a

然后就是可以a.bar()來使用bar()函數了。

一個更復雜的例子

這是一個來自官方的例子

文件結構如下

sound
|-- effects
|   |-- echo.py
|   |-- __init__.py
|   |-- reverse.py
|   `-- surround.py
|-- filters
|   |-- equalizer.py
|   |-- __init__.py
|   |-- karaoke.py
|   `-- vocoder.py
|-- formats
|   |-- aiffread.py
|   |-- aiffwrite.py
|   |-- auread.py
|   |-- auwrite.py
|   |-- __init__.py
|   |-- wavread.py
|   `-- wavwrite.py
`-- __init__.py

你可以將這個package的例子下載下來府怯。如果直接使用import sound來導入這個package,我們可以導入package sound防楷,但是sound的子package(effects,filters,formats)并不會被自動導入牺丙。子package不會被自動導入的原因是因為在sound目錄下的__init__.py文件并沒有任何關于導入子package的代碼。

我們來看下在sound目錄下的__init__.py的代碼

"""An empty sound package

This is the sound package, providing hardly anything!"""


print("sound package is getting imported!")

然后我們導入sound試下

>>> import sound
sound package is getting imported!
>>> sound
<module 'sound' from '/home/bernd/packages/sound/__init__.py'>
>>> sound.effects
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'sound' has no attribute 'effects'

如果你想使用子package的內容复局,但是在父package的__init__.py的文件內并沒有導入冲簿,你需要手動導入

>>> import sound.effects
effects package is getting imported!
>>> sound.effects
<module 'sound.effects' from '/home/bernd/packages/sound/effects/__init__.py'>

如果你希望python幫你自動導入sound.effects你可以往sound目錄下的__init__.py文件寫入

"""An empty sound package

This is the sound package, providing hardly anything!"""

import sound.effects
print("sound package is getting imported!")

那么你下次運行的時候python就會自動幫你導入sound.effects

>>> import sound
effects package is getting imported!
sound package is getting imported!

當然了,除了使用絕對路徑你可以使用相對路徑來導入sound.effects

"""An empty sound package

This is the sound package, providing hardly anything!"""

from . import effects
print("sound package is getting imported!")

這跟linux的命令行比較像肖揣,.代表當前目錄民假,..代表上級目錄

所以你可以在sound.effects__init__.py文件內寫入

from .. import formats

來導入sound.formats浮入。

當你使用sound的時候就會發(fā)現龙优,sound.effectssound.formats都被導入了

>>> import sound
formats package is getting imported!
effects package is getting imported!
sound package is getting imported!

最后我想給你展示下,怎么從sound.effects導入sound.filters.karaoke模塊事秀,將一下代碼加入到sound.effects__init__.py文件中

"""An empty effects package

This is the effects package, providing hardly anything!"""

from .. import formats
from ..filters import karaoke
print("effects package is getting imported!")

激活python的交互環(huán)境以后彤断,嘗試import sound

>>> import sound
formats package is getting imported!
filters package is getting imported!
Module karaoke.py has been loaded!
effects package is getting imported!
sound package is getting imported!

現在我們可以使用karaoke的函數了

>>> sound.filters.karaoke.func1()
Funktion func1 has been called!
>>> 

把你的整個package都導入進來

還是用前面的例子,這一次易迹,我會額外的加入一個叫做foobar的模塊在主目錄宰衙,你可以在這里下載例子

我們嘗試使用*來進行全部的導入

>>> from sound import *
sound package is getting imported!

我們可以看到僅僅是導入了sound這個package但是其他的內容并沒有導入。

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

__all__

我們可以使用__all__這個魔法變量來手動導入模塊和子package睹欲,當你定義了__all____init__.py文件以后供炼,python會根據你在list內給出的元素進行逐個導入

__all__ = ["formats", "filters", "effects", "foobar"]

所以我們可以再次導入試試

>>> from sound import *
sound package is getting imported!
formats package is getting imported!
filters package is getting imported!
effects package is getting imported!
The module foobar is getting imported

看下dir()

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'effects', 'filters', 'foobar', 'formats']
>>>

你會發(fā)現所有模塊都已經被順利導入。

那如果我們僅僅導入sound.effectspackage內所有內容呢窘疮,會發(fā)生什么袋哼,我們import的時候到底import的是什么。

我們看下結果

>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> 

你會發(fā)現他僅僅是導入了sound.effects這個package闸衫,跟你沒有修改sound__init__.py之前是類似情況涛贯,僅僅是導入了這個package。

所以你也可以修改sound.effects__init__.py文件來導入effects內的所有模塊

__all__ = ["echo", "surround", "reverse"]

看下結果

>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
Module echo.py has been loaded!
Module surround.py has been loaded!
Module reverse.py has been loaded!
>>> 
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'echo', 'reverse', 'surround']
>>> 

總結

  1. from package import * 語句中蔚出,如果 __init__.py 中定義了 __all__ 魔法變量弟翘,那么在__all__內的所有元素都會被作為模塊自動被導入(ImportError任然會出現虫腋,如果自動導入的模塊不存在的話)。
  2. 如果 __init__.py 中沒有 __all__ 變量稀余,導出將按照以下規(guī)則執(zhí)行:
    1. 此 package 被導入悦冀,并且執(zhí)行 __init__.py 中可被執(zhí)行的代碼
    2. __init__.py 中定義的 variable 被導入
    3. __init__.py 中被顯式導入的 module 被導入

reference

深入理解 Python package
Packages in Python

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市滚躯,隨后出現的幾起案子雏门,更是在濱河造成了極大的恐慌掸掏,老刑警劉巖丧凤,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異浩螺,居然都是意外死亡仍侥,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門患蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸紊,“玉大人,你說我怎么就攤上這事沼溜∮翁恚” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵找都,是天一觀的道長檐嚣。 經常有香客問我,道長嗡贺,這世上最難降的妖魔是什么鞍帝? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任帕涌,我火速辦了婚禮,結果婚禮上蚓曼,老公的妹妹穿的比我還像新娘。我一直安慰自己床绪,他們只是感情好其弊,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布梭伐。 她就那樣靜靜地躺著,像睡著了一般绩社。 火紅的嫁衣襯著肌膚如雪技掏。 梳的紋絲不亂的頭發(fā)上项鬼,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天绘盟,我揣著相機與錄音,去河邊找鬼龄毡。 笑死,一個胖子當著我的面吹牛祭隔,可吹牛的內容都是我干的路操。 我是一名探鬼主播千贯,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搔谴,長吁一口氣:“原來是場噩夢啊……” “哼敦第!你這毒婦竟也來了店量?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤师幕,失蹤者是張志新(化名)和其女友劉穎诬滩,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體后控,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡浩淘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年吴攒,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片署惯。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡极谊,死狀恐怖安岂,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情域那,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布友瘤,位于F島的核電站檐束,受9級特大地震影響,放射性物質發(fā)生泄漏被丧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一柿究、第九天 我趴在偏房一處隱蔽的房頂上張望蝇摸。 院中可真熱鬧,春花似錦貌夕、人聲如沸民镜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲸鹦。三九已至,卻和暖如春齐板,著一層夾襖步出監(jiān)牢的瞬間嵌戈,已是汗流浹背听皿。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庵朝,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓椎瘟,卻偏偏與公主長得像侄旬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宣羊,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容