三撼班、模塊命名空間
命名空間(名稱空間)中保存了變量名到對(duì)象的映射砰嘁。向命名空間添加變量名的操作過程涉及到綁定變量到指定對(duì)象的操作(以及給該對(duì)象的引用計(jì)數(shù)加 1 )矮湘。改變一個(gè)變量的綁定叫做重新綁定,刪除一個(gè)變量叫做解除綁定磕蛇。
我們?cè)谥耙呀?jīng)介紹過在程序執(zhí)行期間有兩個(gè)或三個(gè)活動(dòng)的命名空間秀撇。 這三個(gè)名稱空間分別是局部命名空間呵燕,全局命名空間和內(nèi)建命名空間,但局部命名空間在執(zhí)行期間是不斷變化的氧苍,所以我們說"兩個(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)來看怎虫,事情是不同的大审。所有局部命名空間的名稱都在局部作用范圍內(nèi)。局部作用范圍以外的所有變量名都在全局作用范圍內(nèi)粮彤。
還要記得在程序執(zhí)行過程中导坟,局部命名空間和作用域會(huì)隨函數(shù)調(diào)用而不斷變化惫周,而全局命名空間是不變的康栈。
2谅将、變量查找
那么作用域的規(guī)則是如何聯(lián)系到命名空間的呢? 它所要做的就是變量名查詢饥臂。訪問一個(gè)變量時(shí)隅熙,解釋器必須在三個(gè)命名空間中的一個(gè)找到它。 首先從局部命名空間開始酵熙,如果沒有找到匾二,解釋器將繼續(xù)查找全局命名空間察藐。如果這也失敗了分飞,它將在內(nèi)建命名空間里查找睹限。
3羡疗、無限制的命名空間
Python 的一個(gè)有用的特性在于你可以在任何需要放置數(shù)據(jù)的地方獲得一個(gè)命名空間叨恨。比如特碳,你可以在任何時(shí)候給函數(shù)添加屬性(使用熟悉的句點(diǎn)屬性標(biāo)識(shí))。
4站宗、文件生成命名空間
模塊最好理解為變量名的封裝梢灭,也就是定義想讓系統(tǒng)其余部分看見變量名的場所敏释。從技術(shù)上來講钥顽,模塊通常相應(yīng)于文件蜂大,而 Python 會(huì)建立模塊對(duì)象,以包含模塊文件內(nèi)所賦值的所有變量名兄墅。簡而言之隙咸,模塊就是命名空間(變量名建立所在的場所)扎瓶,而存在于模塊之內(nèi)的變量名就是模塊對(duì)象的屬性概荷。
5误证、導(dǎo)入和作用域
不導(dǎo)入一個(gè)文件修壕,就無法存取該文件內(nèi)所定義的變量名慈鸠。也就是說青团,你不能自動(dòng)看見另一個(gè)文件內(nèi)的變量名督笆。對(duì)于函數(shù)也是一樣的娃肿,函數(shù)絕對(duì)無法看見其他函數(shù)內(nèi)的變量名料扰,除非它們從物理上處于同一個(gè)函數(shù)內(nèi)。模塊程序代碼絕對(duì)無法看見其他模塊內(nèi)的變量名嫂伞,除非明確地進(jìn)行了導(dǎo)入末早。
在 Python 中然磷,一段程序的作用域完全由程序所處的文件中的實(shí)際位置決定姿搜。作用域絕不會(huì)被函數(shù)調(diào)用或模塊導(dǎo)入影響舅柜。
解釋器執(zhí)行到這些導(dǎo)入語句致份,如果在搜索路徑中找到了指定的模塊氮块,就會(huì)加載它滔蝉。該過程遵循作用域原則蝠引,如果在一個(gè)模塊的頂層導(dǎo)入蛀柴,那么它的作用域就是全局的鸽疾;如果在函數(shù)中導(dǎo)入肮韧,那么它的作用域是局部的弄企。
6拘领、命名空間的嵌套
雖然導(dǎo)入不會(huì)使命名空間發(fā)生向上的嵌套,但確實(shí)會(huì)發(fā)生向下的嵌套笆凌。利用屬性的點(diǎn)號(hào)運(yùn)算路徑乞而,有可能深入到任意嵌套的模塊中并讀取其屬性爪模。
\模塊通過使用自包含的變量的包屋灌,也就是所謂的命名空間提供了將功能化的接口組織為系統(tǒng)的簡單的方法共郭。在一個(gè)模塊文件的頂層定義的所有的變量名都成了被導(dǎo)入的模塊對(duì)象的屬性落塑。導(dǎo)入給予了對(duì)模塊的全局作用域中的變量的讀取權(quán)罐韩。也就是說,在模塊導(dǎo)入時(shí)龙考,模塊文件的全局作用域變成了模塊對(duì)象的命名空間晦款。Python 的模塊允許將獨(dú)立的文件連接成一個(gè)更大的程序系統(tǒng)缓溅。
四坛怪、模塊搜索路徑
通常來說袜匿,導(dǎo)入過程最重要的部分是最一開始的搜索部分居灯,也就是定位要導(dǎo)入的文件怪嫌。在大多數(shù)情況下岩灭,可以依賴模塊導(dǎo)入搜索路徑的自動(dòng)特性川背,完全不需要配置這些路徑熄云。
Python 的模塊搜索路徑是下面這些部分組合而成的結(jié)果:
1缴允、程序的主目錄
2练般、PYTHONPATH 目錄(如果已經(jīng)進(jìn)行了設(shè)置)
3薄料、標(biāo)準(zhǔn)鏈接庫目錄
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)來田巴,而 Python 會(huì)使用你的設(shè)置來擴(kuò)展模塊搜索的路徑钠糊。
因?yàn)?Python 會(huì)先搜索主目錄,當(dāng)導(dǎo)入的文件跨目錄時(shí)壹哺,這個(gè)設(shè)置才顯得格外重要抄伍。也就是說,如果你需要被導(dǎo)入的文件與進(jìn)行導(dǎo)入的文件處在不同目錄時(shí)管宵,可以考慮設(shè)置這個(gè)環(huán)境變量截珍。3攀甚、標(biāo)準(zhǔn)庫目錄
接著,Python 會(huì)自動(dòng)搜索標(biāo)準(zhǔn)庫模塊的安裝目錄岗喉。因?yàn)檫@些一定會(huì)被搜索秋度,通常是不需要添加到 PYTHONPATH 之中或包含到路徑文件中的。
4钱床、.pth 文件目錄
Python 允許用戶通過配置 .pth 文件把有效的目錄添加到模塊搜索路徑中去荚斯,也就是在后綴名 .pth(路徑的意思)的文本文件中一行一行地列出目錄。
簡而言之查牌,當(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)庫模塊 sys 的 path 屬性)來查看這個(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)庫目錄,以及已經(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è)列表肪康,你可以修改將來的導(dǎo)入的搜索路徑荚恶。一旦做了這類修改,就會(huì)對(duì) Python 程序中將要導(dǎo)入的地方產(chǎn)生影響磷支,因?yàn)樗袑?dǎo)入和文件都共享了同一個(gè)sys.path 列表裆甩。因此可以使用這個(gè)技巧,在 Python 程序中動(dòng)態(tài)配置搜索路徑齐唆。不過要小心:如果從路徑中刪除了重要目錄嗤栓,就無法獲取一些關(guān)鍵的工具了。
sys.path 的設(shè)置方法只在修改的 Python 會(huì)話或程序(即進(jìn)程)中才會(huì)存續(xù)箍邮。在Python 程序結(jié)束后茉帅,不會(huì)保留下來。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 語句中省略的哪痰。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)保持不變绒瘦,通常來說称簿,你不應(yīng)該依賴 Python 會(huì)在給定的目錄中選擇何種的文件類型:讓模塊名獨(dú)特一些,或者設(shè)置模塊搜索路徑惰帽,讓模塊選擇的特性更明確一些憨降。
五、模塊的高級(jí)概念
1该酗、核心風(fēng)格: 導(dǎo)入語句的位置
推薦所有的模塊在 Python 模塊的開頭部分導(dǎo)入授药。 而且最好按照這樣的順序:
- Python 標(biāo)準(zhǔn)庫模塊
- Python 第三方模塊
- 應(yīng)用程序自定義模塊
然后使用一個(gè)空行分割這三類模塊的導(dǎo)入語句。 這將確保模塊使用固定的習(xí)慣導(dǎo)入呜魄,有助于減少每個(gè)模塊需要的 import 語句數(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 語句中的模塊名是直接編寫的變量名稱是掰。然而虑鼎,有時(shí)候辱匿,我們的程序可以在運(yùn)行時(shí)以一個(gè)字符串的形式獲取要導(dǎo)入的模塊的名稱键痛。但是我們無法使用import 語句來直接載入以字符串形式給出其名稱的一個(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__ 變量來確定它是在執(zhí)行還是再導(dǎo)入饰恕。使用 __name__ 變量最常見的就是編寫測(cè)試代碼低飒。簡而言之,可以在文件末尾加個(gè) __name__ 判斷語句懂盐,把測(cè)試代碼放到判斷語句中褥赊。
編寫既可以作為命名行工具也可以作為工具庫使用的文件時(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ù)。 明確地說窜管,只把函數(shù)和類定義放入模塊的頂層是良好的模塊編程習(xí)慣散劫。
模塊耦合要降到最低:
我們應(yīng)該盡量的最小化模塊間的耦合性。
模塊應(yīng)該少去修改其他模塊的變量:
使用另一個(gè)模塊定義的全局變量幕帆,這完全是可以的(畢竟這就是客戶端導(dǎo)入服務(wù)的方式)获搏,但是,修改另一個(gè)模塊內(nèi)的全局變量蜓肆,通常是出現(xiàn)設(shè)計(jì)問題的征兆颜凯。
6谋币、頂層代碼語句次序的重要性
當(dāng)模塊首次導(dǎo)入(或重載)時(shí),Python會(huì)從頭到尾的執(zhí)行語句症概。在導(dǎo)入時(shí)蕾额,模塊文件頂層的程序代碼(不在函數(shù)內(nèi))就會(huì)被執(zhí)行。因此彼城,該語句是無法引用文件后面位置賦值的變量名诅蝶。位于函數(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語言介紹
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 —— 簡單語句
Python基礎(chǔ)手冊(cè)18 —— 復(fù)合語句(流程控制語句)
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 —— 包