2013-07-12 18 views
25

Rozważmy:Dlaczego importowanie funkcji z modułu trwa dłużej niż cały sam moduł?

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

trwa znacznie dłużej, aby importować tylko funkcję Dispatch zamiast całego modułu, co wydaje się sprzeczne z intuicją. Czy ktoś mógłby wyjaśnić, dlaczego koszty związane z przyjęciem jednej funkcji są tak złe? Dzięki!

Odpowiedz

33

To dlatego, że:

from win32com.client import Dispatch 

odpowiada:

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 

Ale from win32com.client import Dispatch ma swoje zalety, na przykład jeśli używasz win32com.client.Dispatch wiele razy w kodzie to jest to lepiej przypisać ją do zmiennej, aby zmniejszyć liczbę wyszukiwań. W przeciwnym razie każde połączenie z numerem win32com.client.Dispatch() będzie najpierw wyszukiwać pod kątem win32com, a następnie w win32com, a na końcu Dispatch w wersji win32com.client.


Porównanie Byte-code:

z kodu bajtowego jasno wynika, że ​​liczba kroków wymaganych do from os.path import splitext są większe niż proste 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  

Moduł buforowania:

pamiętać, że po from os.path import splitext nadal można uzyskać dostęp za pomocą modułu ossys.modules ponieważ buforuje python importowanych modułów.

Od docs:

Uwaga W celu zapewnienia skuteczności, każdy moduł jest importowany tylko raz na sesji interpretera. Dlatego, jeśli zmienisz moduły, musisz ponownie uruchomić interpreter - lub, jeśli jest to tylko jeden moduł, który chcesz interaktywnie przetestować , użyj reload(), np. reload(modulename).

Demo:

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'] 

wyjściowe:

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

porównania czasowe:

$ 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

Podoba mi się ta odpowiedź lepiej! –

+3

To wspaniała odpowiedź - zawiera: * pojęcie *, * praktyczny dowód * i * dokumentację standardową *. Najlepsza odpowiedź, jaką każdy może uzyskać. –

+0

Kompleksowe wyjaśnienie, dzięki! – TheoretiCAL

11

Cały moduł nadal musi zostać zaimportowany, aby uzyskać nazwę, z której chcesz ... System operacyjny również buforuje moduł, aby późniejszy dostęp do pliku .pyc był szybszy.

+0

Zapomnij o .pyc prędkością, dzięki! – TheoretiCAL

+0

Chyba podążać za pytaniem, cały moduł może być buforowany, ale czy python również przechwytuje funkcje z modułów? – TheoretiCAL

+0

@JonClements: Nie tylko analizowany, ale importowany. Po 'from foo import bar 'spójrz na' sys.modules [' foo '] ', a zobaczysz dokładnie to samo, co zrobiłeś' import foo'. – abarnert

2

Głównym problemem jest to, że twój kod nie jest w czasie, co myślisz, że jest czas. timieit.timeit() uruchomi instrukcję import w pętli, 100000 razy, ale co najwyżej pierwsza iteracja faktycznie wykona import. Wszystkie inne iteracje sprawdzają po prostu moduł w sys.modules, szukają nazwy globalnych modułów w nazwie Dispatch i dodają tę nazwę do globali modułu importującego. Więc to zasadniczo tylko operacje słownika, a małe odmiany kodu bajtowego staną się widoczne, ponieważ tam względny wpływ w porównaniu do bardzo tanich operacji słownikowych jest duży.

Jeśli, z drugiej strony, mierzysz czas potrzebny do rzeczywistego importu modułu, nie widzisz żadnej różnicy między tymi dwoma podejściami, ponieważ w obu przypadkach czas ten jest całkowicie zdominowany przez rzeczywisty import, i różnice między słownikiem nazw stają się pomijalne. Możemy zmusić powrotny przywóz usuwając moduł z sys.modules w każdej iteracji:

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

Właśnie zdałem sobie sprawę, że pierwszy import tylko faktycznie wykona import, co czyni przykład w moim pytaniu nieco mylącym. Dzięki za wskazanie, jak to się dzieje, że czasy są mniej więcej takie same. – TheoretiCAL

Powiązane problemy