六、包
在 Python 中,導入除了可以指定模塊名之外,也可以指定目錄路徑。為了幫助組織模塊并提供命名層次結構,Python 有一個概念:包。
你可以將程序包視為文件系統上的目錄,將模塊視為目錄中的文件,但是包和模塊并不需要源自文件系統。像文件系統目錄一樣,包是以分層方式組織的,包本身可以包含子包以及常規模塊。事實上,包導入是把計算機上的目錄編成另一個 Python 的命名空間,而包對象的屬性則對應于目錄中所包含的子目錄和模塊文件。
Python 中只有一種模塊類型,不管這個模塊是否是用 Python、C,或者其他語言實現。包是一種特殊的模塊,或者換句話說,所有的包都是模塊,但不是所有的模塊都是包。具體來說,包含 __path__ 屬性的任何模塊都被視為包。
所有模塊都有一個名稱,子包名稱(或者文件模塊名)與其父包名稱之間用點號 .
分隔,類似于 Python 的標準屬性訪問語法。使用標準的 import 和 from-import 語句導入包中的模塊。您可能有一個名為 sys 的模塊和一個名為 email 的軟件包,其中包含一個名為 email.mime 的子包,還有名為 email.mime.text 的子包。
包提供的層次結構對于組織大型系統內的文件會很方便,而且可以簡化模塊搜索路徑的設置。當多個同名的程序文件安裝在某一機器上時,包導入也可以偶爾來解決導入的不確定性。
包是一個有層次的文件目錄結構,它定義了一個由 模塊 和 子包 組成的 Python 應用程序執行環境。Python 1.5 加入了包,用來幫助解決如下問題:
- 為平坦的名稱空間加入有層次的組織結構
- 允許程序員把有聯系的模塊組合到一起
- 允許分發者使用目錄結構而不是一大堆混亂的文件
- 幫助解決有沖突的模塊名稱
Regular packages
Python 定義了兩種類型的包,regular packages 和 namespace packages。
常規包是傳統包,常規包通常實現為包含 __init__.py 文件的目錄。當導入常規包時,這個 __init__.py 文件被隱式執行,它定義的對象被綁定到包名稱空間中的名稱。__init__.py 文件可以包含與任何其他模塊可以包含的相同的 Python 代碼,Python 會在導入時為模塊添加一些其他屬性。
例如,以下文件系統布局定義具有三個子包的頂層 parent 包:導入 parent.one 將隱式執行 parent/__init__.py 和 parent/one/__init__.py 。隨后導入 parent.two 或 parent.three 將執行 parent/two/__init__.py 和 parent/three/__init__.py。
下面的章節主要介紹常規包的概念。
Namespace packages
命名空間包是各種 portions 的組合,其中每個部分都向父包提供子包。其可以存在于文件系統上的不同位置,也可以在 zip 文件,網絡或 Python 在導入期間搜索的任何其他位置找到。命名空間包可以或可以不直接對應于文件系統上的對象;它們可以是沒有具體表示的虛擬模塊。
命名空間包不為其 __path__ 屬性使用普通列表。它們改為使用自定義可迭代類型,如果其父包的路徑發生變化(或對于頂級包的 sys.path),它將在該包中的下一次導入嘗試時自動執行對包部分的新搜索變化。
使用命名空間包,沒有 parent/__init__.py 文件。實際上,在導入搜索期間可能會有多個 parent,其中每個目錄由不同的部分提供。因此,parent/one 可能不在物理上位于parent/two 旁邊。在這種情況下,Python 將為頂層 parent 包創建一個命名空間包,只要它或其中一個子包被導入。
1、包導入基礎
在 import 語句中列舉簡單文件名的地方,可以改成列出路徑的名稱,彼此以點號相隔:
import dir1.dir2.mod
form 語句也是一樣的:
from dir1.dir2.mod import x
這些語句的點號 .
路徑是對應于物理機上的目錄層次的路徑,通過這個路徑可以獲得到文件 mod.py(或類似文件,擴展名可能會有變化)。也就是說,上面的語句是表明了機器上有個目錄 dir1,而 dir1 里面有子目錄 dir2,而 dir2 內包含有一個名為 mod.py (或其他格式文件)的模塊文件。
這些導入意味著,dri1 目錄位于某個目錄 dir0 中,這個 dir0 目錄可以在 Python 模塊搜索路徑中找到。容器目錄 dir0 需要添加在模塊搜索路徑中(除非這是頂層文件的主目錄)。
dir0/dir1/dir2/mod.py
import 語句中的目錄路徑只能是以點號間隔的變量。選擇點號語法,一是考慮到跨平臺,同時也是因為 import 語句中的路徑會變成實際的嵌套對象的路徑。這種語法也意味著,如果你忘記了在 import 語句中省略 .py,就會得到奇怪的錯誤信息。
__init__.py 包文件
如果選擇使用包導入,就必須多遵循一條約束:包導入語句的路徑中的每個目錄內都必須有 __init__.py 這個文件,否則導入包會失敗。
也就是在 dir0/dir1/dir2/mod.py 的路徑下必須保證在 dir1 和 dir2 中必須都含所有一個__init__.py 文件;dir0 是容器,不需要 __init__.py 文件,如果有的話,這個文件也會被忽略,dir0(而非dir0/dir1)必須列在模塊搜索路徑上。
__init__.py 可以包含 Python 程序代碼,就像普通模塊文件。這類文件從某種程度上將就像是 Python 的一種聲明,盡管如此,也可以完全是空的。作為聲明,這些文件可以防止有相同名稱的目錄不小心隱藏在模塊搜索路徑中,而之后才出現真正所需要的模塊。沒有這層保護,Python可能會搜索到和程序代碼無關的目錄,只是因為有一個同名的目錄剛好出現在搜索路徑上位置較前的目錄內。
__init__.py 文件扮演了包初始化的鉤子,替目錄產生模塊命名空間以及使用目錄導入時實現 from * (也就是from ... import *)行為的角色。
1、包的初始化
Python 首次導入某個目錄時,會自動執行該目錄下 __init__.py 文件中的所有程序代碼。因此,這類文件自然就是放置包內文件所需要初始化的代碼的場所。例如,包可以使用其初始化文件,來創建所需要的數據文件、連接數據庫等。
2、模塊命名空間的初始化
在包導入的模型中,腳本內的目錄路徑,在導入后會成真實的嵌套對象路徑。導入的包對應的目錄內的 __init_.py 文件會在導入時執行,其生成的所有變量名都會成為生成的模塊對象的屬性。__init__.py 文件為目錄所創建的包模塊對象提供了命名空間。
3、from * 語句的行為
作為一個高級功能,你可以在 __init__.py 文件內使用 __all__ 列表來定義目錄以 from * 語句形式導入時,需要導出什么。在 __init__.py 文件中,__all__ 列表是指當包(目錄)名稱使用 from * 的時候,應該導入的子模塊的名稱清單。如果沒有設定 __all__ ,from * 語句不會自動加載嵌套于該目錄內的子模塊。取而代之的是,只加載該目錄的 __init__.py 文件中賦值語句定義的變量名,包括改文件中程序代碼明確導入的任何子模塊。
2、包導入實例
當 Python 向下搜索路徑時,import 語句會在每個目錄首次遍歷時,執行該目錄的初始化文件。假定有如下的目錄結構:執行了上面的導入操作后,路徑中的每個目錄名稱都會變成賦值了包模塊對象的屬性,而包模塊對象的命名空間則是由該目錄內的 init.py 文件中所有賦值語句進行初始化的。
包對應的 form 語句和 import 語句
import 語句和包一起使用時,有些不方便,因為你必須經常在程序中重新輸入路徑。為什么使用包導入
包讓導入更具信息性,并可以作為組織工具,簡化模塊的搜索路徑,而且可以解決模糊性。
首先,因為包導入提供了程序文件的目錄信息,因此可以輕松地找到文件,從而可以作為組織工具來使用。沒有包導入時,通常得通過查看模塊搜索路徑才能找出文件。再者,如果根據功能把文件組織成子目錄,包導入會讓模塊扮演的角色更為明顯,也使代碼更具可讀性。包導入也可以大幅簡化 PYTHONPATH 和 .pth 文件搜索路徑設置。實際上,如果所有跨目錄的導入,都是用包導入,并且讓這些包導入都相對于一個共同的根目錄,把所有 Python 的程序代碼都存在其中,在搜索路徑上就只需一個單獨的接入點:通用的根目錄。包導入讓你想導入的文件更明確,從而解決了模糊性。
3、包的相對導入
在包自身的內部的文件中導入包中的模塊可以使用和外部導入相同的路徑語法,但是,它們也可能使用特殊的包內搜索規則來簡化導入語句。也就是說,包內的導入可能相對于包,而不是列出完整的包導入路徑。而且,當文件中導入的模塊的對應文件出現在模塊搜索路徑上許多地方時,可以解決模糊性。
相對導入基礎知識
我們可以在 from 語句 后通過使用點號(.)來指定當前目錄,這可以導入位于同一包中的模塊(所謂的包相對導入),而不是位于模塊導入搜索路徑上某處的模塊(叫做絕對導入)。相對導入的點號用來表示當前文件的目錄。前面再增加一個點號,將執行從當前文件目錄的父目錄相對導入。點號只可以用來對 from 語句執行強制相對導入,而不能對 import 語句這樣。前面沒有點號的 from 語句與 import 語句的行為相同。
4、模塊查找規則總結
簡單模塊名通過搜索 sys.path 路徑列表上的每個目錄來查找,從左到右進行。這個列表由系統默認設置和用戶配置設置組成。
包是帶有一個特殊的 __init__.py 文件的目錄,這使得一個導入中可以使用 A.B.C 目錄路徑語法。
在一個包文件中,常規的 import、from 語句使用和其他地方的導入一樣的 sys.path 搜索規則。包中的相對導入使用 from 語句以及后面的點號,它是相對于包的;也就是說,只檢查包目錄,并且不使用常規的 sys.path 查找。
《Python基礎手冊》系列:
Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數的定義
Python基礎手冊22 —— 函數的參數
Python基礎手冊23 —— 函數的調用
Python基礎手冊24 —— 函數中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包