2009-11-09 38 views
37

我認爲將導入語句放在使用它的片段附近可以使其依賴關係更加清晰,從而提高可讀性。 Python會緩存這個嗎?我應該在乎嗎?這是一個壞主意嗎?Python中的本地導入語句

def Process(): 
    import StringIO 
    file_handle=StringIO.StringIO('hello world') 
    #do more stuff 

for i in xrange(10): Process() 

多一點的理由:這是對於使用該庫的神祕位方法,但是當我重構方法到另一個文件,我不知道我錯過了外部依賴,直到我得到一個運行時錯誤。

+0

這將是一個非常好的「with(import StringIO)as moduleName:syntax – JeremyKun 2012-02-07 21:31:37

+0

@Bean:如果這樣你不必輸入長或笨拙的模塊名稱,那麼有一個簡單的方法:''導入StringIO'然後'sio = StringIO',現在你可以執行'file_handle = sio.StringIO('hello world')'並保存那些寶貴的五個字符,但我會盡量少用這個,因爲它可以使代碼變得更難(這項任務很容易被忽略;非標準的模塊名稱可能會讓人分心) – cvoinescu 2012-05-02 21:30:32

回答

63

其他答案表明import真的有效。

本聲明:

import foo 

大約相當於本聲明:

foo = __import__('foo', globals(), locals(), [], -1) 

也就是說,它會在當前範圍內使用相同的名稱所請求的模塊的變量,並且受讓人它是調用__import__()與該模塊名稱和一堆默認參數的結果。

__import__()功能處理從概念上將字符串('foo')轉換爲模塊對象。模塊緩存在sys.modules,這是第一個__import__()看起來 - 如果sys.modules有一個條目'foo',這就是__import__('foo')將返回,無論它是什麼。它真的不關心這種類型。你可以親自看到這一點;嘗試運行下面的代碼:

import sys 
sys.modules['boop'] = (1, 2, 3) 
import boop 
print boop 

撇開風格擔憂的時刻,有一個import語句中的函數工作你會如何想。如果之前從未導入模塊,則會將其導入並緩存在sys.modules中。然後它將該模塊分配給具有該名稱的局部變量。它確實不是不是修改任何模塊級別的狀態。它確實可能會修改某些全局狀態(向sys.modules添加一個新條目)。

這就是說,我幾乎從來沒有在一個函數裏面使用import。如果導入模塊會在你的程序中產生明顯的減速 - 就像它在靜態初始化中執行長計算一樣,或者它只是一個巨大的模塊 - 並且你的程序實際上很少需要模塊來完成任何事情,它使用的功能。 (如果這太令人厭惡,Guido會跳進他的時間機器並更改Python以防止我們這樣做。)但是,通常,我和一般Python社區將所有導入語句放在模塊範圍的模塊頂部。

+15

它也偶爾可以將您從週期性導入中解救出來(例如:如果您需要使用django導入您的managers.py文件中的模型,並且models.py已經導入了管理器.py文件,通常它會這樣做) – Jiaaro 2011-05-26 20:01:53

+4

明確的答案,我很高興你用3 _not_,因爲2或4會令人困惑。 – matiasg 2014-12-03 20:39:15

10

請參閱PEP 8

進口量始終把在 頂部的文件,只是任何模塊後 意見和文檔字符串,以及模塊全局變量和常量之前。

請注意,這純粹是一種文體選擇,因爲Python將所有import語句視爲相同,無論它們在源文件中聲明的位置如何。不過我會建議你遵循常規做法,因爲這會使你的代碼更易讀。

+2

很酷的鏈接,但是:「打破特定規則的兩個很好的理由:(1)當應用規則會使代碼變得不可讀時,即使對於習慣閱讀遵循規則的代碼的人也是如此。 ...「 – 2009-11-09 04:37:15

+0

這隻適用於頂層導入,函數內部的導入只有在該函數被調用之後纔會被處理,但我通常不鼓勵這樣做,當您有依賴關係時加載的代價非常昂貴,或者在所有環境中都不可用 – 2017-06-12 16:57:59

9

除了風格外,確實導入的模塊只能導入一次(除非在所述模塊上調用reload)。但是,每次撥打import Foo時,都將隱式檢查該模塊是否已加載(通過檢查sys.modules)。

也考慮兩個否則等於功能「拆卸」,其中一個嘗試導入模塊和其他沒有:

>>> def Foo(): 
...  import random 
...  return random.randint(1,100) 
... 
>>> dis.dis(Foo) 
    2   0 LOAD_CONST    1 (-1) 
       3 LOAD_CONST    0 (None) 
       6 IMPORT_NAME    0 (random) 
       9 STORE_FAST    0 (random) 

    3   12 LOAD_FAST    0 (random) 
      15 LOAD_ATTR    1 (randint) 
      18 LOAD_CONST    2 (1) 
      21 LOAD_CONST    3 (100) 
      24 CALL_FUNCTION   2 
      27 RETURN_VALUE   
>>> def Bar(): 
...  return random.randint(1,100) 
... 
>>> dis.dis(Bar) 
    2   0 LOAD_GLOBAL    0 (random) 
       3 LOAD_ATTR    1 (randint) 
       6 LOAD_CONST    1 (1) 
       9 LOAD_CONST    2 (100) 
      12 CALL_FUNCTION   2 
      15 RETURN_VALUE   

我不知道字節碼得到多少轉化爲虛擬機,但如果這是您程序的重要內部循環,那麼您肯定希望通過Foo方法對Bar方法施加一些權重。

一個快速和骯髒timeit測試確實顯示出溫和的速度提高使用Bar時:

$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Foo()" 
200000 loops, best of 3: 10.3 usec per loop 
$ python -m timeit -s "from a import Foo,Bar" -n 200000 "Bar()" 
200000 loops, best of 3: 6.45 usec per loop 
3

當Python解釋器擊中import語句,它開始讀取要導入的文件中的所有函數定義。這解釋了爲什麼有時候,進口可能需要一段時間。

Andrew Hare指出,在開始階段做所有導入背後的想法是一個風格的慣例。但是,您必須記住,通過這樣做,您隱式地讓解釋器檢查該文件是否在第一次導入後已經被導入。當您的代碼文件變大並且您想「升級」您的代碼以刪除或替換某些依賴項時,它也會成爲問題。這將要求您搜索整個代碼文件以查找導入此模塊的所有位置。

我會建議按照慣例,並保持導入在您的代碼文件的頂部。如果你確實想跟蹤函數的依賴關係,那麼我會建議在docstring中爲它添加它們。

+1

「當python解釋器碰到一個導入語句時,它開始讀取文件中的所有函數定義」 - (1)它只發生在模塊的第一次在Python解釋器的當前運行期間被導入(並且導入的模塊被存儲在sys.modules中,以便下次僅需要名稱查找(2)「讀取函數定義」不是它所做的;它執行所有模塊(大部分是'def'和'class'語句)。其他的東西,比如更多的import,設置模塊級的數據結構(有時來自文件)可能需要一段時間。(3)不相關。 – 2009-11-10 09:27:52

0

我可以看到兩種方式,當你需要在本地

  1. 導入用於測試目的或臨時使用,你需要輸入什麼,在這種情況下,你應該把進口在使用的地方。

  2. 有時爲了避免循環依賴,您需要將其導入到函數中,但這意味着您在其他位置遇到問題。

否則爲了效率和一致性,總是將其置於頂端。

8

我已經這樣做了,然後希望我沒有。通常,如果我正在編寫一個函數,並且該函數需要使用StringIO,那麼我可以查看模塊的頂部,查看它是否正在導入,如果不是,則添加它。

假設我不這樣做;假設我在本地函數中添加它。然後假設在某個地方,我或其他人添加了一堆使用StringIO的其他函數。該人將看到模塊的頂部並添加import StringIO。現在你的函數包含的代碼不僅意想不到,而且是多餘的。另外,它違背了我認爲是一個非常重要的原則:不要直接從函數內部修改模塊級別的狀態。

編輯:

其實,事實證明,所有上述是無稽之談。

導入模塊不會修改模塊級別的狀態(它初始化被導入的模塊,如果沒有其他東西,但這不完全相同)。導入您已經導入的其他模塊除了查找sys.modules以及在本地範圍內創建變量外,其他任何操作都不會成本。

知道了這一點,我覺得有點傻,修正了我的代碼中我固定它的所有地方,但那是我的交叉點。

+1

我粗體給你看使用我沒有想到這一點,回想起來這真的很明顯。 – 2009-11-10 01:28:58

+0

如果通過這個,你的意思是「在函數中間導入StringIO」會改變函數定義的模塊範圍的任何內容......這是錯誤的。正如我在我的答案中所解釋的,在函數中間執行「導入」並不會改變定義該函數的模塊狀態中的anyuthing。 無論如何修改模塊級別的狀態有什麼問題嗎?有一個完整的關鍵詞專注於做這件事:「全球化」。 – 2009-11-11 23:06:44

+0

+1爲坦率。 – 2011-03-03 18:21:32