六、包
在 Python 中译打,導(dǎo)入除了可以指定模塊名之外,也可以指定目錄路徑筹我。為了幫助組織模塊并提供命名層次結(jié)構(gòu)扶平,Python 有一個(gè)概念:包帆离。
你可以將程序包視為文件系統(tǒng)上的目錄蔬蕊,將模塊視為目錄中的文件,但是包和模塊并不需要源自文件系統(tǒng)哥谷。像文件系統(tǒng)目錄一樣岸夯,包是以分層方式組織的,包本身可以包含子包以及常規(guī)模塊们妥。事實(shí)上猜扮,包導(dǎo)入是把計(jì)算機(jī)上的目錄編成另一個(gè) Python 的命名空間,而包對(duì)象的屬性則對(duì)應(yīng)于目錄中所包含的子目錄和模塊文件监婶。
Python 中只有一種模塊類型旅赢,不管這個(gè)模塊是否是用 Python、C惑惶,或者其他語(yǔ)言實(shí)現(xiàn)煮盼。包是一種特殊的模塊,或者換句話說(shuō)带污,所有的包都是模塊僵控,但不是所有的模塊都是包。具體來(lái)說(shuō)鱼冀,包含 __path__ 屬性的任何模塊都被視為包报破。
所有模塊都有一個(gè)名稱,子包名稱(或者文件模塊名)與其父包名稱之間用點(diǎn)號(hào) .
分隔千绪,類似于 Python 的標(biāo)準(zhǔn)屬性訪問(wèn)語(yǔ)法充易。使用標(biāo)準(zhǔn)的 import 和 from-import 語(yǔ)句導(dǎo)入包中的模塊。您可能有一個(gè)名為 sys 的模塊和一個(gè)名為 email 的軟件包荸型,其中包含一個(gè)名為 email.mime 的子包盹靴,還有名為 email.mime.text 的子包。
包提供的層次結(jié)構(gòu)對(duì)于組織大型系統(tǒng)內(nèi)的文件會(huì)很方便,而且可以簡(jiǎn)化模塊搜索路徑的設(shè)置鹉究。當(dāng)多個(gè)同名的程序文件安裝在某一機(jī)器上時(shí)宇立,包導(dǎo)入也可以偶爾來(lái)解決導(dǎo)入的不確定性。
包是一個(gè)有層次的文件目錄結(jié)構(gòu)自赔,它定義了一個(gè)由 模塊 和 子包 組成的 Python 應(yīng)用程序執(zhí)行環(huán)境妈嘹。Python 1.5 加入了包,用來(lái)幫助解決如下問(wèn)題:
- 為平坦的名稱空間加入有層次的組織結(jié)構(gòu)
- 允許程序員把有聯(lián)系的模塊組合到一起
- 允許分發(fā)者使用目錄結(jié)構(gòu)而不是一大堆混亂的文件
- 幫助解決有沖突的模塊名稱
Regular packages
Python 定義了兩種類型的包绍妨,regular packages 和 namespace packages润脸。
常規(guī)包是傳統(tǒng)包,常規(guī)包通常實(shí)現(xiàn)為包含 __init__.py 文件的目錄他去。當(dāng)導(dǎo)入常規(guī)包時(shí)毙驯,這個(gè) __init__.py 文件被隱式執(zhí)行,它定義的對(duì)象被綁定到包名稱空間中的名稱灾测。__init__.py 文件可以包含與任何其他模塊可以包含的相同的 Python 代碼爆价,Python 會(huì)在導(dǎo)入時(shí)為模塊添加一些其他屬性。
例如媳搪,以下文件系統(tǒng)布局定義具有三個(gè)子包的頂層 parent 包:導(dǎo)入 parent.one 將隱式執(zhí)行 parent/__init__.py 和 parent/one/__init__.py 铭段。隨后導(dǎo)入 parent.two 或 parent.three 將執(zhí)行 parent/two/__init__.py 和 parent/three/__init__.py。
下面的章節(jié)主要介紹常規(guī)包的概念秦爆。
Namespace packages
命名空間包是各種 portions 的組合序愚,其中每個(gè)部分都向父包提供子包。其可以存在于文件系統(tǒng)上的不同位置等限,也可以在 zip 文件爸吮,網(wǎng)絡(luò)或 Python 在導(dǎo)入期間搜索的任何其他位置找到。命名空間包可以或可以不直接對(duì)應(yīng)于文件系統(tǒng)上的對(duì)象望门;它們可以是沒(méi)有具體表示的虛擬模塊形娇。
命名空間包不為其 __path__ 屬性使用普通列表。它們改為使用自定義可迭代類型怒允,如果其父包的路徑發(fā)生變化(或?qū)τ陧敿?jí)包的 sys.path)埂软,它將在該包中的下一次導(dǎo)入嘗試時(shí)自動(dòng)執(zhí)行對(duì)包部分的新搜索變化。
使用命名空間包纫事,沒(méi)有 parent/__init__.py 文件勘畔。實(shí)際上,在導(dǎo)入搜索期間可能會(huì)有多個(gè) parent丽惶,其中每個(gè)目錄由不同的部分提供炫七。因此,parent/one 可能不在物理上位于parent/two 旁邊钾唬。在這種情況下万哪,Python 將為頂層 parent 包創(chuàng)建一個(gè)命名空間包侠驯,只要它或其中一個(gè)子包被導(dǎo)入。
1奕巍、包導(dǎo)入基礎(chǔ)
在 import 語(yǔ)句中列舉簡(jiǎn)單文件名的地方吟策,可以改成列出路徑的名稱,彼此以點(diǎn)號(hào)相隔:
import dir1.dir2.mod
form 語(yǔ)句也是一樣的:
from dir1.dir2.mod import x
這些語(yǔ)句的點(diǎn)號(hào) .
路徑是對(duì)應(yīng)于物理機(jī)上的目錄層次的路徑的止,通過(guò)這個(gè)路徑可以獲得到文件 mod.py(或類似文件檩坚,擴(kuò)展名可能會(huì)有變化)。也就是說(shuō)诅福,上面的語(yǔ)句是表明了機(jī)器上有個(gè)目錄 dir1匾委,而 dir1 里面有子目錄 dir2,而 dir2 內(nèi)包含有一個(gè)名為 mod.py (或其他格式文件)的模塊文件氓润。
這些導(dǎo)入意味著赂乐,dri1 目錄位于某個(gè)目錄 dir0 中,這個(gè) dir0 目錄可以在 Python 模塊搜索路徑中找到咖气。容器目錄 dir0 需要添加在模塊搜索路徑中(除非這是頂層文件的主目錄)挨措。
dir0/dir1/dir2/mod.py
import 語(yǔ)句中的目錄路徑只能是以點(diǎn)號(hào)間隔的變量。選擇點(diǎn)號(hào)語(yǔ)法采章,一是考慮到跨平臺(tái)运嗜,同時(shí)也是因?yàn)?import 語(yǔ)句中的路徑會(huì)變成實(shí)際的嵌套對(duì)象的路徑。這種語(yǔ)法也意味著悯舟,如果你忘記了在 import 語(yǔ)句中省略 .py,就會(huì)得到奇怪的錯(cuò)誤信息砸民。
__init__.py 包文件
如果選擇使用包導(dǎo)入抵怎,就必須多遵循一條約束:包導(dǎo)入語(yǔ)句的路徑中的每個(gè)目錄內(nèi)都必須有 __init__.py 這個(gè)文件,否則導(dǎo)入包會(huì)失敗岭参。
也就是在 dir0/dir1/dir2/mod.py 的路徑下必須保證在 dir1 和 dir2 中必須都含所有一個(gè)__init__.py 文件反惕;dir0 是容器,不需要 __init__.py 文件演侯,如果有的話姿染,這個(gè)文件也會(huì)被忽略,dir0(而非dir0/dir1)必須列在模塊搜索路徑上秒际。
__init__.py 可以包含 Python 程序代碼悬赏,就像普通模塊文件。這類文件從某種程度上將就像是 Python 的一種聲明娄徊,盡管如此闽颇,也可以完全是空的。作為聲明寄锐,這些文件可以防止有相同名稱的目錄不小心隱藏在模塊搜索路徑中兵多,而之后才出現(xiàn)真正所需要的模塊尖啡。沒(méi)有這層保護(hù),Python可能會(huì)搜索到和程序代碼無(wú)關(guān)的目錄剩膘,只是因?yàn)橛幸粋€(gè)同名的目錄剛好出現(xiàn)在搜索路徑上位置較前的目錄內(nèi)衅斩。
__init__.py 文件扮演了包初始化的鉤子,替目錄產(chǎn)生模塊命名空間以及使用目錄導(dǎo)入時(shí)實(shí)現(xiàn) from * (也就是from ... import *)行為的角色怠褐。
1矛渴、包的初始化
Python 首次導(dǎo)入某個(gè)目錄時(shí),會(huì)自動(dòng)執(zhí)行該目錄下 __init__.py 文件中的所有程序代碼惫搏。因此具温,這類文件自然就是放置包內(nèi)文件所需要初始化的代碼的場(chǎng)所。例如筐赔,包可以使用其初始化文件铣猩,來(lái)創(chuàng)建所需要的數(shù)據(jù)文件、連接數(shù)據(jù)庫(kù)等茴丰。
2达皿、模塊命名空間的初始化
在包導(dǎo)入的模型中,腳本內(nèi)的目錄路徑贿肩,在導(dǎo)入后會(huì)成真實(shí)的嵌套對(duì)象路徑峦椰。導(dǎo)入的包對(duì)應(yīng)的目錄內(nèi)的 __init_.py 文件會(huì)在導(dǎo)入時(shí)執(zhí)行,其生成的所有變量名都會(huì)成為生成的模塊對(duì)象的屬性汰规。__init__.py 文件為目錄所創(chuàng)建的包模塊對(duì)象提供了命名空間汤功。
3、from * 語(yǔ)句的行為
作為一個(gè)高級(jí)功能溜哮,你可以在 __init__.py 文件內(nèi)使用 __all__ 列表來(lái)定義目錄以 from * 語(yǔ)句形式導(dǎo)入時(shí)滔金,需要導(dǎo)出什么。在 __init__.py 文件中茂嗓,__all__ 列表是指當(dāng)包(目錄)名稱使用 from * 的時(shí)候餐茵,應(yīng)該導(dǎo)入的子模塊的名稱清單。如果沒(méi)有設(shè)定 __all__ 述吸,from * 語(yǔ)句不會(huì)自動(dòng)加載嵌套于該目錄內(nèi)的子模塊忿族。取而代之的是,只加載該目錄的 __init__.py 文件中賦值語(yǔ)句定義的變量名蝌矛,包括改文件中程序代碼明確導(dǎo)入的任何子模塊道批。
2、包導(dǎo)入實(shí)例
當(dāng) Python 向下搜索路徑時(shí)朴读,import 語(yǔ)句會(huì)在每個(gè)目錄首次遍歷時(shí)屹徘,執(zhí)行該目錄的初始化文件。假定有如下的目錄結(jié)構(gòu):執(zhí)行了上面的導(dǎo)入操作后簿煌,路徑中的每個(gè)目錄名稱都會(huì)變成賦值了包模塊對(duì)象的屬性,而包模塊對(duì)象的命名空間則是由該目錄內(nèi)的 init.py 文件中所有賦值語(yǔ)句進(jìn)行初始化的鉴吹。
包對(duì)應(yīng)的 form 語(yǔ)句和 import 語(yǔ)句
import 語(yǔ)句和包一起使用時(shí)姨伟,有些不方便,因?yàn)槟惚仨毥?jīng)常在程序中重新輸入路徑豆励。為什么使用包導(dǎo)入
包讓導(dǎo)入更具信息性剿吻,并可以作為組織工具,簡(jiǎn)化模塊的搜索路徑串纺,而且可以解決模糊性丽旅。
首先,因?yàn)榘鼘?dǎo)入提供了程序文件的目錄信息纺棺,因此可以輕松地找到文件榄笙,從而可以作為組織工具來(lái)使用。沒(méi)有包導(dǎo)入時(shí)祷蝌,通常得通過(guò)查看模塊搜索路徑才能找出文件茅撞。再者,如果根據(jù)功能把文件組織成子目錄杆逗,包導(dǎo)入會(huì)讓模塊扮演的角色更為明顯乡翅,也使代碼更具可讀性。包導(dǎo)入也可以大幅簡(jiǎn)化 PYTHONPATH 和 .pth 文件搜索路徑設(shè)置尚洽。實(shí)際上悔橄,如果所有跨目錄的導(dǎo)入,都是用包導(dǎo)入腺毫,并且讓這些包導(dǎo)入都相對(duì)于一個(gè)共同的根目錄癣疟,把所有 Python 的程序代碼都存在其中,在搜索路徑上就只需一個(gè)單獨(dú)的接入點(diǎn):通用的根目錄潮酒。包導(dǎo)入讓你想導(dǎo)入的文件更明確睛挚,從而解決了模糊性。
3急黎、包的相對(duì)導(dǎo)入
在包自身的內(nèi)部的文件中導(dǎo)入包中的模塊可以使用和外部導(dǎo)入相同的路徑語(yǔ)法扎狱,但是侧到,它們也可能使用特殊的包內(nèi)搜索規(guī)則來(lái)簡(jiǎn)化導(dǎo)入語(yǔ)句。也就是說(shuō)淤击,包內(nèi)的導(dǎo)入可能相對(duì)于包匠抗,而不是列出完整的包導(dǎo)入路徑。而且污抬,當(dāng)文件中導(dǎo)入的模塊的對(duì)應(yīng)文件出現(xiàn)在模塊搜索路徑上許多地方時(shí)汞贸,可以解決模糊性。
相對(duì)導(dǎo)入基礎(chǔ)知識(shí)
我們可以在 from 語(yǔ)句 后通過(guò)使用點(diǎn)號(hào)(.)來(lái)指定當(dāng)前目錄印机,這可以導(dǎo)入位于同一包中的模塊(所謂的包相對(duì)導(dǎo)入)矢腻,而不是位于模塊導(dǎo)入搜索路徑上某處的模塊(叫做絕對(duì)導(dǎo)入)。相對(duì)導(dǎo)入的點(diǎn)號(hào)用來(lái)表示當(dāng)前文件的目錄射赛。前面再增加一個(gè)點(diǎn)號(hào)多柑,將執(zhí)行從當(dāng)前文件目錄的父目錄相對(duì)導(dǎo)入。點(diǎn)號(hào)只可以用來(lái)對(duì) from 語(yǔ)句執(zhí)行強(qiáng)制相對(duì)導(dǎo)入,而不能對(duì) import 語(yǔ)句這樣蛔屹。前面沒(méi)有點(diǎn)號(hào)的 from 語(yǔ)句與 import 語(yǔ)句的行為相同削樊。
4、模塊查找規(guī)則總結(jié)
簡(jiǎn)單模塊名通過(guò)搜索 sys.path 路徑列表上的每個(gè)目錄來(lái)查找兔毒,從左到右進(jìn)行漫贞。這個(gè)列表由系統(tǒng)默認(rèn)設(shè)置和用戶配置設(shè)置組成。
包是帶有一個(gè)特殊的 __init__.py 文件的目錄育叁,這使得一個(gè)導(dǎo)入中可以使用 A.B.C 目錄路徑語(yǔ)法迅脐。
在一個(gè)包文件中,常規(guī)的 import豪嗽、from 語(yǔ)句使用和其他地方的導(dǎo)入一樣的 sys.path 搜索規(guī)則谴蔑。包中的相對(duì)導(dǎo)入使用 from 語(yǔ)句以及后面的點(diǎn)號(hào),它是相對(duì)于包的龟梦;也就是說(shuō)隐锭,只檢查包目錄,并且不使用常規(guī)的 sys.path 查找计贰。
《Python基礎(chǔ)手冊(cè)》系列:
Python基礎(chǔ)手冊(cè) 1 —— Python語(yǔ)言介紹
Python基礎(chǔ)手冊(cè) 2 —— Python 環(huán)境搭建(Linux)
Python基礎(chǔ)手冊(cè) 3 —— Python解釋器
Python基礎(chǔ)手冊(cè) 4 —— 文本結(jié)構(gòu)
Python基礎(chǔ)手冊(cè) 5 —— 標(biāo)識(shí)符和關(guān)鍵字
Python基礎(chǔ)手冊(cè) 6 —— 操作符
Python基礎(chǔ)手冊(cè) 7 —— 內(nèi)建函數(shù)
Python基礎(chǔ)手冊(cè) 8 —— Python對(duì)象
Python基礎(chǔ)手冊(cè) 9 —— 數(shù)字類型
Python基礎(chǔ)手冊(cè)10 —— 序列(字符串)
Python基礎(chǔ)手冊(cè)11 —— 序列(元組&列表)
Python基礎(chǔ)手冊(cè)12 —— 序列(類型操作)
Python基礎(chǔ)手冊(cè)13 —— 映射(字典)
Python基礎(chǔ)手冊(cè)14 —— 集合
Python基礎(chǔ)手冊(cè)15 —— 解析
Python基礎(chǔ)手冊(cè)16 —— 文件
Python基礎(chǔ)手冊(cè)17 —— 簡(jiǎn)單語(yǔ)句
Python基礎(chǔ)手冊(cè)18 —— 復(fù)合語(yǔ)句(流程控制語(yǔ)句)
Python基礎(chǔ)手冊(cè)19 —— 迭代器
Python基礎(chǔ)手冊(cè)20 —— 生成器
Python基礎(chǔ)手冊(cè)21 —— 函數(shù)的定義
Python基礎(chǔ)手冊(cè)22 —— 函數(shù)的參數(shù)
Python基礎(chǔ)手冊(cè)23 —— 函數(shù)的調(diào)用
Python基礎(chǔ)手冊(cè)24 —— 函數(shù)中變量的作用域
Python基礎(chǔ)手冊(cè)25 —— 裝飾器
Python基礎(chǔ)手冊(cè)26 —— 錯(cuò)誤 & 異常
Python基礎(chǔ)手冊(cè)27 —— 模塊
Python基礎(chǔ)手冊(cè)28 —— 模塊的高級(jí)概念
Python基礎(chǔ)手冊(cè)29 —— 包