2013-07-12 28 views
25

考慮:爲什麼從模塊導入功能比整個模塊本身花費更長時間?

>>> timeit.timeit('from win32com.client import Dispatch', number=100000) 
0.18883283882571789 
>>> timeit.timeit('import win32com.client', number=100000) 
0.1275979248277963 

它需要更長的顯著僅導入調度功能,而不是整個模塊,這似乎與直覺相反。有人能解釋爲什麼採取單一功能的開銷太糟糕了嗎?謝謝!

回答

33

這是因爲:

from win32com.client import Dispatch 

等同於:

import win32com.client    #import the whole module first 
Dispatch = win32com.client.Dispatch #assign the required attributes to global variables 
del win32com      #remove the reference to module object 

from win32com.client import Dispatch有自身的優勢,例如,如果你在代碼中使用win32com.client.Dispatch多次那麼它的最好把它分配給一個變量,這樣可以減少查找次數。否則,每次撥打win32com.client.Dispatch()將首先搜索搜索win32com,然後client內部win32com,最後Dispatch內部win32com.client


字節碼的比較:

從字節代碼很顯然,對於from os.path import splitext需要的步驟數比簡單import越大。

>>> def func1(): 
    from os.path import splitext 
...  
>>> def func2(): 
    import os.path 
...  
>>> import dis 
>>> dis.dis(func1) 
    2   0 LOAD_CONST    1 (-1) 
       3 LOAD_CONST    2 (('splitext',)) 
       6 IMPORT_NAME    0 (os.path) 
       9 IMPORT_FROM    1 (splitext) 
      12 STORE_FAST    0 (splitext) 
      15 POP_TOP    
      16 LOAD_CONST    0 (None) 
      19 RETURN_VALUE   
>>> dis.dis(func2) 
    2   0 LOAD_CONST    1 (-1) 
       3 LOAD_CONST    0 (None) 
       6 IMPORT_NAME    0 (os.path) 
       9 STORE_FAST    0 (os) 
      12 LOAD_CONST    0 (None) 
      15 RETURN_VALUE  

模塊緩存:

注意from os.path import splitext後,你仍然可以訪問使用sys.modules因爲Python緩存導入的模塊的os模塊。

docs

注爲了提高效率,每個模塊僅進口每 解釋器會話一次。因此,如果您更換模塊,則必須重新啓動解釋器 - 或者,如果它只是一個模塊,您希望以交互方式測試 ,請使用reload()(例如, reload(modulename)

演示:

import sys 
from os.path import splitext 
try: 
    print os 
except NameError: 
    print "os not found" 
try: 
    print os.path 
except NameError: 
    print "os.path is not found" 

print sys.modules['os'] 

輸出:

os not found 
os.path is not found 
<module 'os' from '/usr/lib/python2.7/os.pyc'> 

時序比較:

$ python -m timeit -n 1 'from os.path import splitext' 
1 loops, best of 3: 5.01 usec per loop 
$ python -m timeit -n 1 'import os.path' 
1 loops, best of 3: 4.05 usec per loop 
$ python -m timeit -n 1 'from os import path' 
1 loops, best of 3: 5.01 usec per loop 
$ python -m timeit -n 1 'import os' 
1 loops, best of 3: 2.86 usec per loop 
+1

我更喜歡這個答案! –

+3

它真棒答案 - 包括:*概念*,*實用證明*和* doc標準*。像任何人都能得到的最佳答案。 –

+0

綜合解釋,謝謝! – TheoretiCAL

11

整個模塊仍然需要導入才能從中獲得所需的名稱......您還會發現操作系統正在緩存模塊,因此後續訪問.pyc文件的速度會更快。

+0

忘掉.pyc文件加快,謝謝! – TheoretiCAL

+0

我想跟進問題,整個模塊可以被緩存,但python是否也從模塊捕獲功能? – TheoretiCAL

+0

@JonClements:不只是解析,而是導入。在'from foo import bar'後面,看看'sys.modules ['foo']',你會看到完全一樣的東西,就好像你已經完成了'import foo'一樣。 – abarnert

2

這裏的主要問題是,你的代碼不能計算你認爲它是計時的。 timieit.timeit()將在循環中運行import語句100000次,但最多第一次迭代實際上將執行導入。所有其他迭代只需查找sys.modules中的模塊,在模塊的全局變量中查找名稱Dispatch,並將此名稱添加到導入模塊的全局變量中。所以它基本上只是字典操作,並且字節碼中的小變化將變得可見,因爲與非常便宜的字典操作相比,相對影響很大。

另一方面,如果您測量實際導入模塊所花費的時間,則無法看到兩種方法之間的差異,因爲在這兩種情況下,此時間完全由實際導入爲主,並且與名字字典混淆的差異變得可以忽略不計。我們可以通過刪除sys.modules模塊中的每個迭代迫使reimports:

In [1]: import sys 

In [2]: %timeit from os import path; del sys.modules["os"] 
1000 loops, best of 3: 248 us per loop 

In [3]: %timeit import os.path; del sys.modules["os"] 
1000 loops, best of 3: 248 us per loop 

In [4]: %timeit from os import path 
1000000 loops, best of 3: 706 ns per loop 

In [5]: %timeit import os.path 
1000000 loops, best of 3: 444 ns per loop 
+0

我剛剛意識到第一次導入只會實際執行導入,使我的問題中的例子有點誤導。感謝您指出在時間上的結果大致相同。 – TheoretiCAL

相關問題