模塊是最高級別的程序組織單元,它將程序代碼和數(shù)據(jù)封裝起來以便重用坛善。
模塊是變量名的封裝澎媒,被認為是命名空間搞乏。模塊導入時,模塊文件的全局作用域變成了模塊對象的命名空間旱幼。
在模塊文件頂層定義的所有變量名都變成了模塊對象的屬性查描。
模塊有三個角色:
- 代碼重用
- 系統(tǒng)命名空間的劃分
- 實現(xiàn)共享服務和數(shù)據(jù)
1 模塊導入
在導入語句中的模塊名有兩個作用:
- 識別需要加載的外部文檔
- 賦值給被導入模塊的變量
模塊定義的對象會在import
執(zhí)行時創(chuàng)建。
導入時運行時的運算柏卤,程序第一次導入指定文件時冬三,會執(zhí)行以下三個步驟:
- 找到模塊文件
- 編輯成字節(jié)碼(如果需要)
- 執(zhí)行模塊代碼,創(chuàng)建其中定義的對象
這三個步驟只在程序執(zhí)行時缘缚,模塊第一次導入的時候執(zhí)行勾笆。在這之后導入相同模塊時會跳過這三個步驟,只是提取內存中已加載的模塊對象桥滨。
Python把加載的模塊存儲到sys.modules
中窝爪,并在第一次導入時檢查該表。如果模塊不存在齐媒,則執(zhí)行這三個步驟蒲每。
1.1 搜索
Python的模塊搜索路徑由以下幾部分組成:
- 程序的主目錄
- PYTHONPATH環(huán)境變量的目錄(如果設置了的話)
- 標準鏈接庫目錄
- 任何
.pth
路徑文件中的內容(如果存在的話)
它們組合起來就是模塊搜索路徑sys.path
。其中第一和第三項是自動定義的喻括,第二和第四項可以拓展搜索路徑邀杏。Python會從頭到尾搜索它們的組合,也就是從左到右搜索sys.path
中的目錄唬血。
1.1.1 主目錄
Python首先在主目錄搜索導入的文件望蜡。主目錄的含義與如何運行代碼相關唤崭。運行一個程序時,主目錄是包含程序頂層腳本的目錄脖律;在交互式模式下谢肾,主目錄是當前的工作目錄。
因為Python總是優(yōu)先搜索這個目錄小泉,所以它會覆蓋其它目錄中的同名模塊芦疏,小心不要以這種方式覆蓋標準庫模塊。
1.1.2 PYTHONPATH目錄
之后膏孟,Python會從左到右搜索PYTHONPATH環(huán)境變量(如果設置了的話)中列出的目錄眯分。可以在該列表中包括所有想要導入的目錄柒桑,從而擴展模塊搜索路徑弊决。
只有導入的文件跨目錄時,即被導入的文件與進行導入的文件不在同一個目錄時魁淳,這個設置才顯得格外重要飘诗。
1.1.3 標準庫目錄
接著,Python會自動搜索標準庫模塊所在的目錄界逛。這些目錄一定會被搜索昆稿,所以不需要添加到PYTHONPATH或.pth
路徑文件中。
1.1.4 .pth
文件目錄
在后綴為.pth
文件中一行一行列出目錄息拜,可以作為PYTHONPATH的一種替代方案溉潭。Python會把文件中每行羅列的目錄從頭到尾添加到模塊搜索路徑列表的最后。
Python可能會把當前工作目錄也加進來少欺,放在PYTHONPATH之后喳瓣,標準庫目錄之前。從命令行啟動程序時赞别,當前工作目錄和頂層文件的主目錄(也就是程序文件所在的目錄)不一定相同畏陕。每次執(zhí)行程序時,當前工作目錄可能會發(fā)生變化仿滔,所以不應該依賴這個值導入惠毁。
程序啟動時,Python自動將頂層文件的主目錄(或者指定當前工作目錄的空字符串)崎页,所有PYTHONPATH目錄鞠绰,標準庫目錄,以及已經(jīng)創(chuàng)建的.pth
路徑文件中的目錄合并飒焦。
Python會選擇搜索路徑中第一個符合導入文件名的文件洞豁。import b
語句可能會加載:
- 源文件
b.py
- 字節(jié)碼文件
b.pyc
- 目錄
b
(包導入時) - 編譯后的擴展模塊(通常用C\C++編寫),導入時使用動態(tài)鏈接(比如
.so
,.dll
丈挟,或.pyd
) - 用C編寫的內置模塊,并使用靜態(tài)鏈接
- ZIP文件組件志电,導入時自定解壓
- 內存中的映像(對于fronzen可執(zhí)行文件)
- Java類(Jython版本的Python中)
- .NET組件(IronPython版本的Python中)
如果不同目錄中有b.py
和b.so
兩個文件曙咽,Python會從左到右搜索sys.path
,并加載最先出現(xiàn)(最左邊)的文件挑辆。如果同一個目錄下有b.py
和b.so
例朱,則不一定加載哪個文件。
1.2 編譯
遍歷模塊搜索路徑鱼蝉,并找到源代碼文件后洒嗤,如果有必要,Python會將其編譯成字節(jié)碼文件魁亦。
Python比較字節(jié)碼文件和源文件的時間戳渔隶。如果字節(jié)碼文件比源文件舊,則會在程序運行時自動生成字節(jié)碼文件洁奈;否則會跳過編譯步驟间唉。
如果Python在搜索路徑上只發(fā)現(xiàn)了字節(jié)碼文件,而沒有源代碼文件利术,則會直接加載字節(jié)碼文件呈野。
當文件導入時會進行編譯。只有被導入的文件才會在機器上留下.pyc
字節(jié)碼文件印叁,從而提高之后的導入速度被冒。頂層文件的字節(jié)碼文件在內部使用后就丟棄了。
1.3 運行
import
操作最后的步驟是執(zhí)行模塊的字節(jié)碼轮蜕。文件中的所有語句會被依次執(zhí)行昨悼。在這個步驟中,對任何變量名的賦值運算都會產生模塊文件的屬性肠虽。
2 模塊使用
在Python 3中幔戏,from *
語句只能用于模塊文件的頂層,而不能用于函數(shù)中税课。
import
和from
都是隱性的賦值語句:
-
import
將整個模塊對象賦值給一個對象名 -
from
將一個或多個變量名賦值給另一個模塊中的同名對象
以from
復制的變量名會變成對共享對象的引用闲延。與函數(shù)參數(shù)一樣,對已取出的變量名重新賦值韩玩,不會影響其復制之處的模塊垒玲;但是修改一個已取出的可變對象,則會影響導入模塊內的對象找颓。例如文件small.py
:
x = 1
y = [1, 2]
% python
>>> from small import x, y
>>> x = 42
>>> y[0] = 42
這里x
并不是一個共享的可變對象合愈,但y
是。導入者中的變量名y
和被導入者都引用了同一個列表對象。所以在其中一個地方修改佛析,也會影響另一個地方的對象益老。
>>> import small
>>> small.x
1
>>> small.y
[42, 2]
以from
復制而來的變量名與其來源的文件之間沒有聯(lián)系。要實際修改另一個文件中的全局變量名寸莫,必須使用import
捺萌。
% python
>>> from small import x, y
>>> x = 42 # 只修改了本地的x
>>> import small
>>> small.x = 42 # 修改了另一個模塊中的x
from
的第一步也是普通的導入操作,它總是會把整個模塊導入內容(如果還沒有導入的話)膘茎。
3 模塊命名空間
在模塊文件頂層(而不是函數(shù)或者類的主體內)每一個賦值了的變量名都會變成該模塊的屬性桃纯。
模塊的命名空間可以通過__dict__
屬性或dir(M)
獲取。
變量的含義一定是由源代碼中的賦值語句的位置決定的披坏。
4 包導入
Python首次導入某個目錄時态坦,會自動執(zhí)行該目錄下__init__.py
文件中的所有代碼。所以棒拂,可以在該文件內放置包內文件所需要的初始化代碼伞梯。
可以在__init__.py
文件中使用__all__
列表,在其中定義目錄以from *
語句導入時需要導出的子模塊的名稱着茸。如果沒有設置__all__
壮锻,則from *
語句不會自動加載嵌套與該目錄內的子模塊。
4.1 包相對導入
Python 3中包導入的變化:
- 修改了模塊導入時搜索路徑的語義涮阔,它默認會跳過包自身的目錄猜绣,只檢查搜索路徑的其它組件,這叫“絕對”導入敬特。
- 擴展了
from
語法掰邢,允許顯式地要求導入只搜索包自身的目錄,這叫“相對”導入伟阔。
from
語句使用點號導入位于同一個包中的模塊(相對導入)辣之,而不是導入位于模塊搜索路徑上某處的模塊(絕對導入):
- 只在包內搜索,不會搜索模塊搜索路徑上某處的同名文件皱炉。直接效果就是包模塊覆蓋了外部的模塊怀估。
- 在Python 3中,包代碼的常規(guī)導入(前面不含點號)默認是絕對的——忽略包含包自身的目錄合搅,只在
sys.path
上的目錄搜索多搀。
相對導入適用于只在包內導入,并且只能用于from
語句灾部。
Python 3中的模塊查找規(guī)則:
- 簡單模塊名(
import A
)通過搜索sys.path
列表中的每個目錄查找康铭。 - 包是帶有
__init__.py
文件的模塊目錄,它可以使用A.B.C
語法赌髓。其中A
目錄位于sys.path
搜索路徑中从藤。 - 在一個包內的模塊文件中催跪,常規(guī)的
import
語句使用sys.path
搜索規(guī)則。如果包內的模塊文件使用前面帶點號的from
語句時夷野,則它相對于包導入懊蒸。也就是說,只檢查包目錄悯搔,并不使用sys.path
常規(guī)查找榛鼎。
5 其它
5.1 最小化from *
的破壞
在變量前加下劃線,可以防止客戶端使用from *
語句導入模塊時鳖孤,把其中的變量名復制出去。也可以在模塊頂層把變量名的字符串列表賦值給__all__
變量抡笼。
5.2 用名稱字符串導入模塊
有兩種方式可以用模塊的字符串名稱導入模塊:
- 把一條導入語句構建為一個字符串苏揣,并將它傳遞給
exec()
內置函數(shù). - 使用內置函數(shù)
__import__()
。