一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Python Import系統(tǒng)給粗心人設(shè)下的“陷阱”

 River_LaLaLa 2016-08-22

Pythonimport系統(tǒng)是非常強(qiáng)大的,但是也非常復(fù)雜。直到Python 3.3版本的發(fā)布,都沒有關(guān)于之前預(yù)計的import語義的全面的解釋,甚至跟著3.3版本的發(fā)布,sys.path如何初始化的細(xì)節(jié)也仍然需要搞清楚。

即使3.3版本清除了許多東西,它仍舊需要搞定許多后向兼容性問題,這些問題可能導(dǎo)致一些奇怪的行為。并且,為了搞清一些第三方框架的運行機(jī)制,我們也需要充分了解3.3版本。

此外,即使不使用任何導(dǎo)入系統(tǒng)中奇異的特性,在郵件列表或者像Stack Overflow一樣的Q&A網(wǎng)站中也經(jīng)常出現(xiàn)相當(dāng)多的常見的錯誤。

這篇短文的內(nèi)容僅僅理論上向前包含至Python 2.6版本。大多數(shù)內(nèi)容也適合于早期版本,但我不會對2.6以前的版本細(xì)節(jié)給出任何解釋。

丟失的__init__.py陷阱

這個陷阱適用于2.x版本,也包括3.2及3.2之前的3.x版本。

Python 3.3之前,文件系統(tǒng)目錄,以及zipfile中的目錄,必須包含一個__init__.py文件以使它被識別為Python的包目錄。即使當(dāng)包被導(dǎo)入時沒有初始化代碼要運行,解釋器仍然需要一個空的__init__.py文件以便在那個目錄下能夠找到任何模塊或者子包。

這一情況在Python 3.3中改變了:現(xiàn)在任何一個在sys.path中的目錄,如果和要查找的包名稱一致,那么它將被視為該包的可以起作用的模塊或子包。

__init__.py的陷阱

這是一個在Python 3.3中增加的全新的陷阱,是由于修改之前的陷阱而帶來的:如果一個sys.path所導(dǎo)入的包的一個子目錄下也包含一個__init__.py文件,則Python解釋器會創(chuàng)建一個僅僅包含來自于該目錄下的單目錄包,而不是像之前一節(jié)描述的一樣,去尋找所有具有相同名稱的子目錄。

即使在sys.path中存在其他的不包括__init__.py文件子目錄但是和要找的包名稱相同,問題也同樣會發(fā)生。

這一復(fù)雜情況是由于后向兼容性限制而強(qiáng)加于我們的——如果沒有這個問題,當(dāng)Python 3.3讓用戶可選是否在包中需要創(chuàng)建__init__.py文件的時候,一些現(xiàn)存的代碼可能會崩潰。

然而,這一點也是很有用的,因為它使得顯式地聲明一個包已經(jīng)完成,不再接受額外貢獻(xiàn)代碼變得可能。所有的標(biāo)準(zhǔn)庫目前都是這樣工作的,雖然一些包可能會開放它們的命名空間來在未來版本中接受第三方的貢獻(xiàn)代碼(特別的,encodings包將確定在Python 3.4時開放)。

雙重引用陷阱

緊接著的這個陷阱存在于目前所有的Python版本中,包括Python 3.3,并且可以用下面一句話總結(jié):永遠(yuǎn)不要直接向Python路徑中添加一個包目錄,或者包內(nèi)的任何目錄。

這樣做的原因是在那個目錄下的每一個模塊現(xiàn)在都潛在地有兩個不同的可以訪問的名字:作為頂級模塊(由于目錄在sys.path中)以及作為包的子模塊(如果高一級的包含包本身的目錄也在sys.path中)。

舉個例子,Django(直到并包括1.3版本)在為特定站點創(chuàng)建應(yīng)用時的做法是錯誤的——這個應(yīng)用最后可以在模塊命名空間中被作為app以及site.app來接入,并且事實上存在兩份不同的模塊的副本。如果有任何有意義的可變的模塊級的狀態(tài),上述情況會導(dǎo)致困惑,所以這一行為從1.4版本中默認(rèn)的文件夾結(jié)構(gòu)中移除了(特定站點的應(yīng)用將一直需要像Django版本說明中敘述的一樣,完全匹配站點名稱才可以)。

不幸的是,這仍然是十分容易違反的規(guī)則,因為如果你試圖從命令行通過文件名而不是使用-m開關(guān)去運行一個包內(nèi)的模塊,它就會自動發(fā)生。

考慮一個簡單的包,其布局如下(我在我自己的工程里專門沿著這幾條線使用了這樣的包布局——許多人討厭在像這樣的包目錄里做嵌套測試,而喜歡平行的結(jié)構(gòu),但是我更喜歡使用顯式的相對的導(dǎo)入方式來保證模塊測試與包名稱獨立這樣的能力。


這個布局令人驚訝的是所有的下列調(diào)用test_foo.py的方法可能都不起作用,因為導(dǎo)入機(jī)制壞掉了(或者說不能通過像這樣import example.foo或者from example import foo找到example,或者不能在一個非包的或者頂級包以上的包中像from .. import foo一樣的相對引用,或者如果一些其他的子模塊碰巧覆蓋了測試所用的頂級包的名稱,例如一個負(fù)責(zé)序列化的example.json模塊或者一個測試機(jī)模塊example.tests.unittest,一些更加難以理解的錯誤可能會發(fā)生):


沒錯,如果你嘗試那個長長的包含所有調(diào)用方法的列表,它很可能損壞了
,并且如果你既對Python導(dǎo)入系統(tǒng)工作的方式不夠熟悉,又對它如何初始化不夠熟悉,那么錯誤消息就沒有任何意義。(注意到如果工程僅僅顯式地使用了包內(nèi)引用的相對導(dǎo)入,則上面的最后兩條命令可能在Python 3.3以及未來版本中可以工作。任何引用一個頂級包并進(jìn)行絕對導(dǎo)入的做法將仍然會出錯。

長期以來,用這種啟動方式唯一能讓sys.path正確的方法是或者在test_foo.py中手動設(shè)置(很少有Python的新手,甚至許多老手都不知道怎么做)或者確保導(dǎo)入模塊而不是直接執(zhí)行它:


然而,從Python 2.6之后,下面命令仍然可以正常工作:


這最后一種方式就是我在用Python編程時喜歡使用的shell命令——離開我的工作目錄,設(shè)置到工程目錄,然后使用-m開關(guān)來執(zhí)行相關(guān)的子模塊,例如測試或者命令行工具。如果我需要在不同的目錄下工作,好的,這就是為什么我喜歡開著多個shell會話。

當(dāng)我正在使用一個嵌入式測試用例作為例子時,當(dāng)你為了確保sys.path正確初始化了而沒有在父目錄使用-m開關(guān)去在包中直接執(zhí)行一個腳本時,類似的問題隨時都會發(fā)生(例如1.4版本之前的Django工程布局會在當(dāng)從包內(nèi)運行manage.py時產(chǎn)生問題,它會將包目錄放入sys.path以致導(dǎo)致這個雙重導(dǎo)入問題——1.4版本之后的布局通過把manage.py移到包目錄外面而解決了這一問題)。

事實是大多數(shù)從命令行調(diào)用Python代碼在當(dāng)代碼位于一個包內(nèi)時都會崩潰,而兩個可以工作的方式又對當(dāng)前工作目錄非常敏感,這對于新手來說非常困惑。我個人相信這是導(dǎo)致Python包復(fù)雜并且很難被正確使用這一觀點的關(guān)鍵因素。

這個問題甚至不限于命令行——如果test_foo.pyIDLE中打開并且你試圖通過F5運行它時,或者你試圖在一個圖像化的文件瀏覽器中通過點擊它來運行時,它就會像通過命令行直接運行一樣失敗。

sys.path中不要寫包目錄這一規(guī)則的存在有一個原因,即解釋器當(dāng)確定sys.path[0]是所有錯誤的根源時它自己也不會參照這一條規(guī)則。

然而,即使在未來版本的Python中在這個部分有許多改善(參見PEP 395),這個陷阱也會在所有當(dāng)前版本中存在。

執(zhí)行主模塊兩次

這是上述雙重引用問題的一個變種,它不需要任何錯誤的sys.path條目。

對于當(dāng)主模塊被作為普通模塊導(dǎo)入的情形來說非常特別,實際上它會產(chǎn)生同一個模塊的兩個不同名稱的實例。

正如任何雙重導(dǎo)入問題,如果存儲在__main__中的狀態(tài)對于程序正確運行十分重要,或者在主模塊中有一些頂級代碼執(zhí)行了不止一次會產(chǎn)生未知的副作用,之后,這個復(fù)制品也會產(chǎn)生復(fù)雜的意想不到的錯誤。

這僅僅是為什么在更加復(fù)雜的應(yīng)用中主模塊需要保持代碼最少的一個原因——通常將大多數(shù)的功能移到在單獨的模塊里的一個函數(shù)或者一個對象中并在主模塊中導(dǎo)入該模塊會更加魯棒。那樣,不經(jīng)意得執(zhí)行主模塊兩次將變得沒有害處。保證主模塊精簡也可以避免伴隨著對象序列化以及多線程包的一些潛在的問題。

命名覆蓋陷阱

另一個常見的陷阱,特別對于初學(xué)者來說,是使用一個本地模塊名導(dǎo)致覆蓋了程序所依賴的標(biāo)準(zhǔn)庫的或是第三方的包或者模塊。一個特別意想不到的碰到這個陷阱的情況是對一個腳本使用這樣的名字,因為這會結(jié)合之前執(zhí)行主模塊兩次陷阱導(dǎo)致問題。例如,如果嘗試學(xué)習(xí)更多關(guān)于Pythonsocket模塊,你可能傾向于命名你的實驗?zāi)_本為socket.py。事實證明這是一個壞主意,因為使用這樣的名字意味著Python解釋器可以不再去標(biāo)準(zhǔn)庫中尋找真正的socket模塊,因為當(dāng)前目錄里的這個socket模塊擋住了去路:


舊的字節(jié)碼文件陷阱

緊跟著之前小節(jié)的例子之后,假設(shè)我們決定通過重命名文件來修復(fù)我們錯誤的腳本名。在Python 2中,我們會發(fā)現(xiàn)這仍然不起作用:


很明顯,一些奇怪的事情發(fā)生了,因為我們看到在錯誤追蹤中顯示一個注釋行出了問題。事實上,由我們之前錯誤的導(dǎo)入動作而緩存的字節(jié)碼文件仍然存在,并且導(dǎo)致了這一問題,但是當(dāng)Python試圖在錯誤追蹤中顯示出錯的源代碼行時,它轉(zhuǎn)而去標(biāo)準(zhǔn)庫模塊中尋找源代碼行。刪除舊的字節(jié)碼文件可以讓它正確運行:


這一特殊的陷阱在Python 3.2以及之后的版本已經(jīng)徹底去除了。在這些版本中,解釋器可以區(qū)別獨立的字節(jié)碼文件(例如上文中的socket.pyc)和緩存的字節(jié)碼文件(存儲在自動生成的__pycache__目錄下)。如果相應(yīng)的源文件不存在,后者會被解釋器忽略掉,所以上述的為源文件重命名如期工作了:


然而,需要注意的是,如果Python 2留下了一個獨立的字節(jié)碼文件,混用Python 2Python 3會導(dǎo)致問題:


如果你不是某個Python實現(xiàn)的核心開發(fā)人員,導(dǎo)入舊的字節(jié)碼的問題很可能當(dāng)重命名Python源文件時發(fā)生。而對于Python實現(xiàn)的開發(fā)人員,它則可能在我們使用負(fù)責(zé)首先產(chǎn)生字節(jié)碼的編譯器部件的任何時刻發(fā)生——這就是為什么CPythonMakefile中包含一個make pycremoval目標(biāo)。

子模塊被加入包命名空間的陷阱

許多人已經(jīng)體驗過了在僅僅導(dǎo)入了子模塊所在的包而去使用該子模塊時存在的問題了:


然而很少被人知的一件事是,當(dāng)一個子模塊在無論任何地方加載時,它會被自動地添加到包的全局命名空間中:


當(dāng)在一個__init__.py文件中導(dǎo)入或者定義一個同當(dāng)前包的子模塊擁有相同名字的值時,結(jié)果可能會更讓你驚訝。如果子模塊在導(dǎo)入或者定義了相同名字的值之后的任意位置被任何模塊加載了,它將會在__init__.py的全局命名空間中覆蓋已經(jīng)導(dǎo)入的或者定義的名字。

更多的奇怪的陷阱

上面提到的都是一些平常的陷阱,但是還存在其他陷阱,尤其是如果你開始著手于擴(kuò)展或者重寫默認(rèn)import系統(tǒng)的工作時。

最后我希望對這些增加一些細(xì)節(jié)描述:

  •  __import__的怪異的簽名

  • 模塊全局變量(__import__, __path__, __package__)的影響

  • 3.3版本之前線程的問題

  • 3.3版本之前默認(rèn)的機(jī)器缺少對PEP 302的支持

  • 3.3版本之前命名空間包中的非合作的包的部分

  • sys.path[0]初始化變量

  • 關(guān)于pickle, multiprocessing和主模塊的問題的更多內(nèi)容(參見PEP 395

  • __main__不總是一個頂級包(多虧了-m

  • 事實上,在導(dǎo)入過程中,模塊是允許在sys.modules中替換他們自己的

  • __file__可能不指代一個實際文件系統(tǒng)的位置

  • 自從3.2之后,你不能僅僅添加c或者o來獲得緩存的字節(jié)碼文件


英文原文:http://python-notes./en/latest/python_concepts/import_traps.html
譯者:LuCima


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    一区二区在线激情视频| 国产内射一级一片内射高清视频| 欧美精品在线观看国产| 中文字幕精品人妻一区| 在线观看免费视频你懂的| 国产精品欧美一级免费| 福利视频一区二区三区| 国产精品福利一级久久| 欧美一区二区三区性视频| 久久精品国产亚洲av麻豆| 久久中文字幕中文字幕中文| 五月婷日韩中文字幕四虎| 精品少妇人妻av一区二区蜜桃| 日本乱论一区二区三区| 91人妻人澡人人爽人人精品| 日韩午夜老司机免费视频| 隔壁的日本人妻中文字幕版 | 日韩午夜老司机免费视频| 亚洲中文字幕剧情在线播放| 九九热这里有精品20| 亚洲品质一区二区三区| 色哟哟哟在线观看视频| 国产日韩欧美在线播放| 精品一区二区三区人妻视频| 好吊视频一区二区在线| 台湾综合熟女一区二区| 精品推荐久久久国产av| 激情图日韩精品中文字幕| 国产亚洲精品俞拍视频福利区| 日韩欧美综合中文字幕| 欧美极品欧美精品欧美| 国产又粗又爽又猛又黄的 | 日韩精品在线观看完整版| 欧洲日韩精品一区二区三区| 大香蕉伊人精品在线观看| 精品人妻一区二区三区免费看| 国产日韩精品激情在线观看 | 色婷婷亚洲精品综合网| 日韩人妻av中文字幕| 丰满熟女少妇一区二区三区| 丝袜美女诱惑在线观看|