三、模塊命名空間
命名空間(名稱空間)中保存了變量名到對(duì)象的映射。向命名空間添加變量名的操作過程涉及到綁定變量到指定對(duì)象的操作(以及給該對(duì)象的引用計(jì)數(shù)加 1 )。改變一個(gè)變量的綁定叫做重新綁定,刪除一個(gè)變量叫做解除綁定。
我們?cè)谥耙呀?jīng)介紹過在程序執(zhí)行期間有兩個(gè)或三個(gè)活動(dòng)的命名空間。 這三個(gè)名稱空間分別是局部命名空間,全局命名空間和內(nèi)建命名空間,但局部命名空間在執(zhí)行期間是不斷變化的,所以我們說(shuō)"兩個(gè)或三個(gè)"。 從命名空間中訪問這些變量名依賴于它們的加載順序,或是系統(tǒng)加載這些命名空間的順序。
Python 解釋器首先加載內(nèi)建命名空間。 它由 __builtins__ 模塊中的名字構(gòu)成。 隨后加載執(zhí)行文件的全局命名空間,它會(huì)在模塊開始執(zhí)行后變?yōu)榛顒?dòng)命名空間。 這樣我們就有了兩個(gè)活動(dòng)的名稱空間。
如果在執(zhí)行期間調(diào)用了一個(gè)函數(shù),那么將創(chuàng)建出第三個(gè)命名空間,即局部命名空間。 我們可以通過 globals() 和 locals() 內(nèi)建函數(shù)判斷出某一名字屬于哪個(gè)命名空間。
1、命名空間和變量作用域
命名空間保存的是純粹意義上的變量名和對(duì)象間的映射關(guān)系,而作用域還指出了從用戶代碼的哪些物理位置可以訪問到這些變量名。
注意每個(gè)命名空間都是一個(gè)單獨(dú)的單元。但從作用域的觀點(diǎn)來(lái)看,事情是不同的。所有局部命名空間的名稱都在局部作用范圍內(nèi)。局部作用范圍以外的所有變量名都在全局作用范圍內(nèi)。
還要記得在程序執(zhí)行過程中,局部命名空間和作用域會(huì)隨函數(shù)調(diào)用而不斷變化,而全局命名空間是不變的。
2、變量查找
那么作用域的規(guī)則是如何聯(lián)系到命名空間的呢? 它所要做的就是變量名查詢。訪問一個(gè)變量時(shí),解釋器必須在三個(gè)命名空間中的一個(gè)找到它。 首先從局部命名空間開始,如果沒有找到,解釋器將繼續(xù)查找全局命名空間。如果這也失敗了,它將在內(nèi)建命名空間里查找。
3、無(wú)限制的命名空間
Python 的一個(gè)有用的特性在于你可以在任何需要放置數(shù)據(jù)的地方獲得一個(gè)命名空間。比如,你可以在任何時(shí)候給函數(shù)添加屬性(使用熟悉的句點(diǎn)屬性標(biāo)識(shí))。
4、文件生成命名空間
模塊最好理解為變量名的封裝,也就是定義想讓系統(tǒng)其余部分看見變量名的場(chǎng)所。從技術(shù)上來(lái)講,模塊通常相應(yīng)于文件,而 Python 會(huì)建立模塊對(duì)象,以包含模塊文件內(nèi)所賦值的所有變量名。簡(jiǎn)而言之,模塊就是命名空間(變量名建立所在的場(chǎng)所),而存在于模塊之內(nèi)的變量名就是模塊對(duì)象的屬性。
5、導(dǎo)入和作用域
不導(dǎo)入一個(gè)文件,就無(wú)法存取該文件內(nèi)所定義的變量名。也就是說(shuō),你不能自動(dòng)看見另一個(gè)文件內(nèi)的變量名。對(duì)于函數(shù)也是一樣的,函數(shù)絕對(duì)無(wú)法看見其他函數(shù)內(nèi)的變量名,除非它們從物理上處于同一個(gè)函數(shù)內(nèi)。模塊程序代碼絕對(duì)無(wú)法看見其他模塊內(nèi)的變量名,除非明確地進(jìn)行了導(dǎo)入。
在 Python 中,一段程序的作用域完全由程序所處的文件中的實(shí)際位置決定。作用域絕不會(huì)被函數(shù)調(diào)用或模塊導(dǎo)入影響。
解釋器執(zhí)行到這些導(dǎo)入語(yǔ)句,如果在搜索路徑中找到了指定的模塊,就會(huì)加載它。該過程遵循作用域原則,如果在一個(gè)模塊的頂層導(dǎo)入,那么它的作用域就是全局的;如果在函數(shù)中導(dǎo)入,那么它的作用域是局部的。
6、命名空間的嵌套
雖然導(dǎo)入不會(huì)使命名空間發(fā)生向上的嵌套,但確實(shí)會(huì)發(fā)生向下的嵌套。利用屬性的點(diǎn)號(hào)運(yùn)算路徑,有可能深入到任意嵌套的模塊中并讀取其屬性。
\模塊通過使用自包含的變量的包,也就是所謂的命名空間提供了將功能化的接口組織為系統(tǒng)的簡(jiǎn)單的方法。在一個(gè)模塊文件的頂層定義的所有的變量名都成了被導(dǎo)入的模塊對(duì)象的屬性。導(dǎo)入給予了對(duì)模塊的全局作用域中的變量的讀取權(quán)。也就是說(shuō),在模塊導(dǎo)入時(shí),模塊文件的全局作用域變成了模塊對(duì)象的命名空間。Python 的模塊允許將獨(dú)立的文件連接成一個(gè)更大的程序系統(tǒng)。
四、模塊搜索路徑
通常來(lái)說(shuō),導(dǎo)入過程最重要的部分是最一開始的搜索部分,也就是定位要導(dǎo)入的文件。在大多數(shù)情況下,可以依賴模塊導(dǎo)入搜索路徑的自動(dòng)特性,完全不需要配置這些路徑。
Python 的模塊搜索路徑是下面這些部分組合而成的結(jié)果:
1、程序的主目錄
2、PYTHONPATH 目錄(如果已經(jīng)進(jìn)行了設(shè)置)
3、標(biāo)準(zhǔn)鏈接庫(kù)目錄
4、任何 .pth 文件的內(nèi)容(如果存在的話)


1、主目錄
Python 首先會(huì)在主目錄內(nèi)搜索導(dǎo)入的文件。當(dāng)你運(yùn)行一個(gè)程序的時(shí)候,這個(gè)目錄就是你運(yùn)行程序頂層腳本文件時(shí)所在的目錄。當(dāng)在交互模式下工作時(shí),這一入口就是你當(dāng)前的工作目錄。
因?yàn)檫@個(gè)目錄總是先被搜索,如果程序完全位于單一目錄,所有導(dǎo)入都會(huì)自動(dòng)工作,而不需要配置路徑。另一方面,由于這個(gè)目錄是先搜索的,其文件也將覆蓋路徑上的其他目錄中具有同樣名稱的模塊。
2、PYTHONPATH 目錄
搜索完主目錄之后如果沒有發(fā)現(xiàn)相應(yīng)的文件,Python會(huì)從左至右(假設(shè)你設(shè)置了的話)搜索 PYTHONPATH 環(huán)境變量設(shè)置中羅列出的所有目錄。PYTHONPATH 是設(shè)置包含 Python 程序文件的目錄的列表,這些目錄可以是用戶定義的或平臺(tái)特定的目錄名。你可以把想導(dǎo)入的目錄都加進(jìn)來(lái),而 Python 會(huì)使用你的設(shè)置來(lái)擴(kuò)展模塊搜索的路徑。
因?yàn)?Python 會(huì)先搜索主目錄,當(dāng)導(dǎo)入的文件跨目錄時(shí),這個(gè)設(shè)置才顯得格外重要。也就是說(shuō),如果你需要被導(dǎo)入的文件與進(jìn)行導(dǎo)入的文件處在不同目錄時(shí),可以考慮設(shè)置這個(gè)環(huán)境變量。
3、標(biāo)準(zhǔn)庫(kù)目錄
接著,Python 會(huì)自動(dòng)搜索標(biāo)準(zhǔn)庫(kù)模塊的安裝目錄。因?yàn)檫@些一定會(huì)被搜索,通常是不需要添加到 PYTHONPATH 之中或包含到路徑文件中的。
4、.pth 文件目錄
Python 允許用戶通過配置 .pth 文件把有效的目錄添加到模塊搜索路徑中去,也就是在后綴名 .pth(路徑的意思)的文本文件中一行一行地列出目錄。
簡(jiǎn)而言之,當(dāng)內(nèi)含目錄名稱的文本文件放到適當(dāng)目錄時(shí),也可以概括地扮演與PYTHONPATH 環(huán)境變量設(shè)置相同的角色。
當(dāng)存在目錄的時(shí)候,Python 會(huì)把文件每行所羅列的目錄從頭到尾加到模塊搜索路徑列表的最后。實(shí)際上,Python 會(huì)將收集它所找到的所有路徑文件中的目錄名,并且過濾掉任何重復(fù)的和不存在的目錄。
搜索路徑的 PYTHONPATH 和路徑文件部分允許我們調(diào)整導(dǎo)入查找文件的地方。 搜索路徑的配置可能隨平臺(tái)以及 Python 版本而異。取決于你所使用的平臺(tái),附加的目錄也可能自動(dòng)加入模塊搜索路徑。
5、sys.path 列表
如果你想查看模塊搜索路徑在機(jī)器上的實(shí)際配置,可以通過打印內(nèi)置的 sys.path 列表(也就是標(biāo)準(zhǔn)庫(kù)模塊 sys 的 path 屬性)來(lái)查看這個(gè)路徑。目錄名稱的字符串列表就是Python 內(nèi)部實(shí)際的搜索路徑。導(dǎo)入時(shí),Python 會(huì)由左至右搜索這個(gè)列表。
sys.path 是模塊搜索的路徑。Python 在程序啟動(dòng)時(shí)進(jìn)行配置,自動(dòng)將頂級(jí)文件的主目錄(或者指定當(dāng)前工作目錄的一個(gè)空字符串)、任何 PYTHONPATH 目錄、標(biāo)準(zhǔn)庫(kù)目錄,以及已經(jīng)創(chuàng)建的任何 .pth 文件路徑的內(nèi)容合并。得到一個(gè) Python 在每次導(dǎo)入一個(gè)新文件的時(shí)候查找的目錄名的字符串列表。這個(gè) sys.path 列表可以幫你確認(rèn)你所做的搜索路徑的設(shè)置值:如果在列表中看不到設(shè)置值,就需要重新檢查你的設(shè)置。
如果你知道在做什么,這個(gè)列表也提供一種方式,讓腳本手動(dòng)調(diào)整其搜索路徑。通過修改 sys.path 這個(gè)列表,你可以修改將來(lái)的導(dǎo)入的搜索路徑。一旦做了這類修改,就會(huì)對(duì) Python 程序中將要導(dǎo)入的地方產(chǎn)生影響,因?yàn)樗袑?dǎo)入和文件都共享了同一個(gè)sys.path 列表。因此可以使用這個(gè)技巧,在 Python 程序中動(dòng)態(tài)配置搜索路徑。不過要小心:如果從路徑中刪除了重要目錄,就無(wú)法獲取一些關(guān)鍵的工具了。
sys.path 的設(shè)置方法只在修改的 Python 會(huì)話或程序(即進(jìn)程)中才會(huì)存續(xù)。在Python 程序結(jié)束后,不會(huì)保留下來(lái)。PYTHONPATH 和 .pth 文件路徑配置時(shí)保存在操作系統(tǒng)中,而不是執(zhí)行的 Python 程序中。PYTHONPATH 和 .pth 文件提供了更改持久的路徑修改方法。因此使用這種配置方法更全局一些:機(jī)器上的每個(gè)程序都會(huì)去查找PYTHONPATH 和 .pth,而且在程序結(jié)束后,他們還存在著。
修改完成后,你就可以加載自己的模塊了。 只要這個(gè)列表中的某個(gè)目錄包含這個(gè)文件, 它就會(huì)被正確導(dǎo)入。 當(dāng)然, 這個(gè)方法是把目錄追加在搜索路徑的尾部。 如果你有特殊需要, 那么應(yīng)該使用列表的 insert() 方法操作 ,把目錄追加在搜索路徑的首部。
6、模塊文件選擇
文件名的后綴(例如.py)是刻意從 import 語(yǔ)句中省略的。Python 會(huì)選擇搜索路徑中第一個(gè)符合導(dǎo)入文件名的文件。
- 源代碼文件:b.py
- 字節(jié)碼文件:b.pyc
- 目錄:b,包導(dǎo)入
- 編譯擴(kuò)展模塊(通常是C或C++編寫),導(dǎo)入時(shí)使用動(dòng)態(tài)連接
- 用 C 編寫的編譯好的內(nèi)置模塊,并通過靜態(tài)連接至 Python。
- ZIP 文件組件,導(dǎo)入時(shí)會(huì)自動(dòng)解壓縮
- 內(nèi)存映像,對(duì)于 frozen 可執(zhí)行文件。
如果在不同目錄中有 b.py 和 b.so,Python 總是在由左至右搜索 sys.path 時(shí),加載模塊搜索路徑那些目錄中最先出現(xiàn)(最左邊的)相符文件。但是,如果實(shí)在相同的目錄中找到這兩個(gè)文件,Python會(huì)遵循一個(gè)標(biāo)準(zhǔn)的調(diào)訓(xùn)順序,不過這種順序不保證永遠(yuǎn)保持不變,通常來(lái)說(shuō),你不應(yīng)該依賴 Python 會(huì)在給定的目錄中選擇何種的文件類型:讓模塊名獨(dú)特一些,或者設(shè)置模塊搜索路徑,讓模塊選擇的特性更明確一些。
五、模塊的高級(jí)概念
1、核心風(fēng)格: 導(dǎo)入語(yǔ)句的位置
推薦所有的模塊在 Python 模塊的開頭部分導(dǎo)入。 而且最好按照這樣的順序:
- Python 標(biāo)準(zhǔn)庫(kù)模塊
- Python 第三方模塊
- 應(yīng)用程序自定義模塊
然后使用一個(gè)空行分割這三類模塊的導(dǎo)入語(yǔ)句。 這將確保模塊使用固定的習(xí)慣導(dǎo)入,有助于減少每個(gè)模塊需要的 import 語(yǔ)句數(shù)目。
2、從 ZIP 文件中導(dǎo)入模塊
在 2.3 版中,Python 加入了從 ZIP 歸檔文件導(dǎo)入模塊的功能。 如果你的搜索路徑中存在一個(gè)包含 Python 模塊(.py, .pyc, or .pyo 文件)的 .zip 文件,導(dǎo)入時(shí)會(huì)把 ZIP 文件當(dāng)作目錄處理,在文件中搜索模塊。
如果要導(dǎo)入的一個(gè) ZIP 文件只包含 .py 文件, 那么 Python 不會(huì)為其添加對(duì)應(yīng)的 .pyc 文件,這意味著如果一個(gè) ZIP 歸檔沒有匹配的 .pyc 文件時(shí),導(dǎo)入速度會(huì)相對(duì)慢一點(diǎn)。
3、用字符串格式的模塊名導(dǎo)入模塊
一條 import 或 from 語(yǔ)句中的模塊名是直接編寫的變量名稱。然而,有時(shí)候,我們的程序可以在運(yùn)行時(shí)以一個(gè)字符串的形式獲取要導(dǎo)入的模塊的名稱。但是我們無(wú)法使用import 語(yǔ)句來(lái)直接載入以字符串形式給出其名稱的一個(gè)模塊,Python 期待一個(gè)變量名,而不是字符串。


4、__name__ 和 __main__
每個(gè)模塊都有個(gè) __name__ 的內(nèi)置屬性,Python 會(huì)自動(dòng)設(shè)置該屬性:
如果文件是以頂層程序文件執(zhí)行,在啟動(dòng)時(shí),__name__ 就會(huì)設(shè)置為字符串 "__main__" 。
如果文件被導(dǎo)入,__name__ 就會(huì)被設(shè)成模塊名。
結(jié)果就是模塊可以檢測(cè)自己的 __name__ 變量來(lái)確定它是在執(zhí)行還是再導(dǎo)入。使用 __name__ 變量最常見的就是編寫測(cè)試代碼。簡(jiǎn)而言之,可以在文件末尾加個(gè) __name__ 判斷語(yǔ)句,把測(cè)試代碼放到判斷語(yǔ)句中。
編寫既可以作為命名行工具也可以作為工具庫(kù)使用的文件時(shí),__name__ 技巧也很好用。
5、模塊設(shè)計(jì)理念
就像函數(shù)一樣,模塊也有設(shè)計(jì)方面的折中考慮:需要思考哪些函數(shù)和類要放進(jìn)模塊以及模塊通信機(jī)制等。
總是在Python的模塊內(nèi)編寫代碼:
沒有辦法寫出不在某個(gè)模塊之內(nèi)的程序代碼。事實(shí)上,在交互模式提示符下輸入的程序代碼,其實(shí)是存在于內(nèi)置模塊 __main__ 之內(nèi)。交互模式提示符獨(dú)特之處就在于程序是執(zhí)行后就立刻丟棄,以及表達(dá)式結(jié)果是自動(dòng)打印的。
加載模塊會(huì)導(dǎo)致這個(gè)模塊被"執(zhí)行"。 也就是被導(dǎo)入模塊的頂層代碼將直接被執(zhí)行。 這通常包括設(shè)定全局變量以及類和函數(shù)的聲明。 如果有檢查 __name__ 的操作,那么它也會(huì)被執(zhí)行。
當(dāng)然,這樣的執(zhí)行可能不是我們想要的結(jié)果。 你應(yīng)該把盡可能多的代碼封裝到函數(shù)。 明確地說(shuō),只把函數(shù)和類定義放入模塊的頂層是良好的模塊編程習(xí)慣。
模塊耦合要降到最低:
我們應(yīng)該盡量的最小化模塊間的耦合性。
模塊應(yīng)該少去修改其他模塊的變量:
使用另一個(gè)模塊定義的全局變量,這完全是可以的(畢竟這就是客戶端導(dǎo)入服務(wù)的方式),但是,修改另一個(gè)模塊內(nèi)的全局變量,通常是出現(xiàn)設(shè)計(jì)問題的征兆。
6、頂層代碼語(yǔ)句次序的重要性
當(dāng)模塊首次導(dǎo)入(或重載)時(shí),Python會(huì)從頭到尾的執(zhí)行語(yǔ)句。在導(dǎo)入時(shí),模塊文件頂層的程序代碼(不在函數(shù)內(nèi))就會(huì)被執(zhí)行。因此,該語(yǔ)句是無(wú)法引用文件后面位置賦值的變量名。位于函數(shù)主體內(nèi)的代碼直到函數(shù)被調(diào)用后才會(huì)運(yùn)行。因?yàn)楹瘮?shù)內(nèi)的變量名在函數(shù)實(shí)際執(zhí)行前都不會(huì)解析,通常可以引用文件內(nèi)任意地方的變量。
《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 —— 包