Importing Python Modules

簡介

import 和 from-import 對於Python新手經常帶來嚴重的混淆,不過相信大家一旦清楚它們的實現機理後就不會對使用他們有任何困惑了。
這篇文章將努力理清import和from-import 相關的一切。

import modules的幾種方式

Python提供至少三種方式來import modules。使用import、from或者內置的__import__function,篇幅所限本文不討論其他的非主流方式。
下面是這幾種方式的實現原理:
  • import X 導入module X,並且在當前命名空間創建到X的引用。換言之,import X後就可以使用X.name使用模塊X中的東東了。
  • from X import * 導入module X,並且在當前命名空間創建到X中所有public對象(即除去名稱以"_”開頭的所有對象)的引用。亦即執行這條語句後,可以直接使用名字使用module X中的東西。但是因爲X自身是未定義的,所以無法使用X.name。命名重複時會使用較新的版本,如果X中該名稱已經指向其他對象你的模塊也不會察覺。這也是該方法的危險之處,雖然用起來方便但容易不知覺間產生BUG。
  • from X import a, b, c 導入module X,並且在當前命名空間創建給定對象的引用,現在可以直接使用a、b和c了。
  • X = __import__(‘X’) 與import X比較相似,不同之處在於:1)可以使用一個string傳遞module的名字 2)可在當前命名空間將其賦值給一個變量(這在導入的module名稱不確定或希望動態導入module時非常有用)

應該採用哪一種?

簡而言之:儘量用import。
以下幾處情形例外:
  • Module文檔要求使用from-import。常見的例子是Tkinter,其被設計爲僅向當前命名空間添加widget classes和相關的常量。如果使用import Tkinter會使你的代碼可讀性很差,故通常不推薦這樣做。
  • 導入一個包中的組件。當只需要導入包中一個特定的子module時,使用from io.drivers import zip通常要比import io.drivers.zip方便一些,因爲前者可以讓我們簡單地用zip就可以使用這個module,而不是使用它的全名io.drivers.zip。在這種情況下,from-import起到的作用很像簡化的import,而且沒有什麼命名混淆的風險。
  • 在執行前不清楚module的名字。在這種情況下應該使用__import__(module_name),其中module_name是Python string,這樣可以在變更module名稱時不需要改動代碼。
  • 對自己將做什麼非常清楚。如果確認如此那儘可以去用from import,但是一定要三思而後行;-)

Python是如何導入module的?

當Python導入module時,它首先檢查module註冊表(sys.modules)確認是否該module已經被導入過,如果存在就使用已導入的module代替它。
否則,Python將執行以下步驟:
  1. 創建一個新的空module對象(其本質是一個dictionary)
  2. 將該module對象插入到sys.modules dictionary中
  3. 加載module的代碼對象(如果需要會先編譯這個module)
  4. 在新module的命名空間執行該module的代碼對象,代碼中賦值的所有變量在該module對象裏可用。通過該module對象中Execute the module code object in the new module’s namespace. All variables assigned by the code will be available via the module object.
這意味着導入一個已經加載的module性能消耗是非常小的,Python只需要在dictionary中查找下module的名字就可以了。

其他要點

使用module作爲腳本

如果你將一個module作爲腳本運行,即直接將其名稱給編譯器而非導入它,那它將以名稱__main__加載。
如果稍後在程序中加載同一個module,它將被重新加載並以其真實的名稱重新執行,所以如果不細心的話可能會做兩次同樣的事。

循環導入

在Python中,像def、class和import之類的語句都是聲明。
module在導入時被執行,但新的函數和類並不會添加到module的命名空間中直到執行def或class進行聲明,這在循環導入會有很明顯的影響。
比如module X導入module Y並定義一個函數spam:
    # module X
    import Y
    def spam():
        print "function in module x"
如果從主程序中導入X,Python將加載X的代碼並執行它。當Python到import Y這一行聲明時,它加載Y的代碼並轉而執行Y的代碼。
此時,Python已經在sys.modules中加載了X和Y。但X還不包含任何東西,def spam這行聲明還未執行。
現在如果Y導入X(出現循環導入),它將得到一個指向空module X對象的引用,但如果試圖調用函數X.spam將會失敗,因爲此時雖然存在X但並不存在X.spam:
    # module Y
    from X import spam # doesn't work: spam isn't defined yet!
這個問題和使用from-import無關,即使換用import也會如此:
    # module Y
    import X
    X.spam() # doesn't work either: spam isn't defined yet!
解決的方法就是重構代碼來避免循環導入,通常可以通過將部分內容拆分到單獨的module來解決。這種解決方式類似於C++中解決循環引用時使用前置聲明將聲明和定義分離的方法,即將衝突的執行代碼分拆到其他文件。
或者將import移到module的末尾,比如上面的例子如果將import Y移到module X的末尾就可以一切正常了,但其實並不總奏效,萬一X.spam使用過Y就又出錯了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章