第五部分 模塊

模塊是最高級別的程序組織單元,它將程序代碼和數據封裝起來以便重用。

模塊是變量名的封裝,被認為是命名空間。模塊導入時,模塊文件的全局作用域變成了模塊對象的命名空間。

在模塊文件頂層定義的所有變量名都變成了模塊對象的屬性。

模塊有三個角色:

  • 代碼重用
  • 系統命名空間的劃分
  • 實現共享服務和數據

1 模塊導入

在導入語句中的模塊名有兩個作用:

  • 識別需要加載的外部文檔
  • 賦值給被導入模塊的變量

模塊定義的對象會在import執行時創建。

導入時運行時的運算,程序第一次導入指定文件時,會執行以下三個步驟:

  • 找到模塊文件
  • 編輯成字節碼(如果需要)
  • 執行模塊代碼,創建其中定義的對象

這三個步驟只在程序執行時,模塊第一次導入的時候執行。在這之后導入相同模塊時會跳過這三個步驟,只是提取內存中已加載的模塊對象。

Python把加載的模塊存儲到sys.modules中,并在第一次導入時檢查該表。如果模塊不存在,則執行這三個步驟。

1.1 搜索

Python的模塊搜索路徑由以下幾部分組成:

  • 程序的主目錄
  • PYTHONPATH環境變量的目錄(如果設置了的話)
  • 標準鏈接庫目錄
  • 任何.pth路徑文件中的內容(如果存在的話)

它們組合起來就是模塊搜索路徑sys.path。其中第一和第三項是自動定義的,第二和第四項可以拓展搜索路徑。Python會從頭到尾搜索它們的組合,也就是從左到右搜索sys.path中的目錄。

1.1.1 主目錄

Python首先在主目錄搜索導入的文件。主目錄的含義與如何運行代碼相關。運行一個程序時,主目錄是包含程序頂層腳本的目錄;在交互式模式下,主目錄是當前的工作目錄。

因為Python總是優先搜索這個目錄,所以它會覆蓋其它目錄中的同名模塊,小心不要以這種方式覆蓋標準庫模塊。

1.1.2 PYTHONPATH目錄

之后,Python會從左到右搜索PYTHONPATH環境變量(如果設置了的話)中列出的目錄。可以在該列表中包括所有想要導入的目錄,從而擴展模塊搜索路徑。

只有導入的文件跨目錄時,即被導入的文件與進行導入的文件不在同一個目錄時,這個設置才顯得格外重要。

1.1.3 標準庫目錄

接著,Python會自動搜索標準庫模塊所在的目錄。這些目錄一定會被搜索,所以不需要添加到PYTHONPATH或.pth路徑文件中。

1.1.4 .pth文件目錄

在后綴為.pth文件中一行一行列出目錄,可以作為PYTHONPATH的一種替代方案。Python會把文件中每行羅列的目錄從頭到尾添加到模塊搜索路徑列表的最后。

Python可能會把當前工作目錄也加進來,放在PYTHONPATH之后,標準庫目錄之前。從命令行啟動程序時,當前工作目錄和頂層文件的主目錄(也就是程序文件所在的目錄)不一定相同。每次執行程序時,當前工作目錄可能會發生變化,所以不應該依賴這個值導入。

程序啟動時,Python自動將頂層文件的主目錄(或者指定當前工作目錄的空字符串),所有PYTHONPATH目錄,標準庫目錄,以及已經創建的.pth路徑文件中的目錄合并。

Python會選擇搜索路徑中第一個符合導入文件名的文件。import b語句可能會加載:

  • 源文件b.py
  • 字節碼文件b.pyc
  • 目錄b(包導入時)
  • 編譯后的擴展模塊(通常用C\C++編寫),導入時使用動態鏈接(比如.so.dll,或.pyd
  • 用C編寫的內置模塊,并使用靜態鏈接
  • ZIP文件組件,導入時自定解壓
  • 內存中的映像(對于fronzen可執行文件)
  • Java類(Jython版本的Python中)
  • .NET組件(IronPython版本的Python中)

如果不同目錄中有b.pyb.so兩個文件,Python會從左到右搜索sys.path,并加載最先出現(最左邊)的文件。如果同一個目錄下有b.pyb.so,則不一定加載哪個文件。

1.2 編譯

遍歷模塊搜索路徑,并找到源代碼文件后,如果有必要,Python會將其編譯成字節碼文件。

Python比較字節碼文件和源文件的時間戳。如果字節碼文件比源文件舊,則會在程序運行時自動生成字節碼文件;否則會跳過編譯步驟。

如果Python在搜索路徑上只發現了字節碼文件,而沒有源代碼文件,則會直接加載字節碼文件。

當文件導入時會進行編譯。只有被導入的文件才會在機器上留下.pyc字節碼文件,從而提高之后的導入速度。頂層文件的字節碼文件在內部使用后就丟棄了。

1.3 運行

import操作最后的步驟是執行模塊的字節碼。文件中的所有語句會被依次執行。在這個步驟中,對任何變量名的賦值運算都會產生模塊文件的屬性。

2 模塊使用

在Python 3中,from *語句只能用于模塊文件的頂層,而不能用于函數中。

importfrom都是隱性的賦值語句:

  • import將整個模塊對象賦值給一個對象名
  • from將一個或多個變量名賦值給另一個模塊中的同名對象

from復制的變量名會變成對共享對象的引用。與函數參數一樣,對已取出的變量名重新賦值,不會影響其復制之處的模塊;但是修改一個已取出的可變對象,則會影響導入模塊內的對象。例如文件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復制而來的變量名與其來源的文件之間沒有聯系。要實際修改另一個文件中的全局變量名,必須使用import

% python
>>> from small import x, y
>>> x = 42    # 只修改了本地的x

>>> import small
>>> small.x = 42   # 修改了另一個模塊中的x

from的第一步也是普通的導入操作,它總是會把整個模塊導入內容(如果還沒有導入的話)。

3 模塊命名空間

在模塊文件頂層(而不是函數或者類的主體內)每一個賦值了的變量名都會變成該模塊的屬性。

模塊的命名空間可以通過__dict__屬性或dir(M)獲取。

變量的含義一定是由源代碼中的賦值語句的位置決定的。

4 包導入

Python首次導入某個目錄時,會自動執行該目錄下__init__.py文件中的所有代碼。所以,可以在該文件內放置包內文件所需要的初始化代碼。

可以在__init__.py文件中使用__all__列表,在其中定義目錄以from *語句導入時需要導出的子模塊的名稱。如果沒有設置__all__,則from *語句不會自動加載嵌套與該目錄內的子模塊。

4.1 包相對導入

Python 3中包導入的變化:

  1. 修改了模塊導入時搜索路徑的語義,它默認會跳過包自身的目錄,只檢查搜索路徑的其它組件,這叫“絕對”導入。
  2. 擴展了from語法,允許顯式地要求導入只搜索包自身的目錄,這叫“相對”導入。

from語句使用點號導入位于同一個包中的模塊(相對導入),而不是導入位于模塊搜索路徑上某處的模塊(絕對導入):

  1. 只在包內搜索,不會搜索模塊搜索路徑上某處的同名文件。直接效果就是包模塊覆蓋了外部的模塊。
  2. 在Python 3中,包代碼的常規導入(前面不含點號)默認是絕對的——忽略包含包自身的目錄,只在sys.path上的目錄搜索。

相對導入適用于只在包內導入,并且只能用于from語句。

Python 3中的模塊查找規則:

  1. 簡單模塊名(import A)通過搜索sys.path列表中的每個目錄查找。
  2. 包是帶有__init__.py文件的模塊目錄,它可以使用A.B.C語法。其中A目錄位于sys.path搜索路徑中。
  3. 在一個包內的模塊文件中,常規的import語句使用sys.path搜索規則。如果包內的模塊文件使用前面帶點號的from語句時,則它相對于包導入。也就是說,只檢查包目錄,并不使用sys.path常規查找。

5 其它

5.1 最小化from *的破壞

在變量前加下劃線,可以防止客戶端使用from *語句導入模塊時,把其中的變量名復制出去。也可以在模塊頂層把變量名的字符串列表賦值給__all__變量。

5.2 用名稱字符串導入模塊

有兩種方式可以用模塊的字符串名稱導入模塊:

  1. 把一條導入語句構建為一個字符串,并將它傳遞給exec()內置函數.
  2. 使用內置函數__import__()
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容