Подтвердить что ты не робот

Почему для импорта функции из модуля требуется больше времени, чем весь модуль?

Рассмотрим:

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

Требуется значительно больше времени, чтобы импортировать только функцию Dispatch, а не весь модуль, что кажется интуитивным счетчиком. Может ли кто-нибудь объяснить, почему накладные расходы на выполнение одной функции настолько плохи? Спасибо!

4b9b3361

Ответ 1

Это потому, что:

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 вы все равно можете получить доступ к модулю os, используя sys.modules, потому что python кэширует импортированные модули.

От docs:

Примечание. По соображениям эффективности каждый модуль импортируется только один раз за интерпретатор. Поэтому, если вы меняете свои модули, вы должны перезапустите интерпретатор - или, если его только один модуль, который вы хотите протестировать интерактивно использовать reload(), например. 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']

выход:

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

Ответ 2

Весь модуль еще нужно импортировать, чтобы получить нужное вам имя... Вы также обнаружите, что ОС кэширует модуль, поэтому последующий доступ к файлу .pyc будет быстрее.

Ответ 3

Основная проблема здесь заключается в том, что ваш код не синхронизирует то, что вы считаете временем. timieit.timeit() будет запускать оператор import в цикле 100000 раз, но самое большее первая итерация фактически выполнит импорт. Все остальные итерации просто ищут модуль в sys.modules, найдите имя Dispatch в глобальных глоссариях модулей и добавьте это имя в глобалы модуля импорта. Таким образом, это существенно только словарные операции, и небольшие вариации в байтовом коде станут видимыми, поскольку относительное влияние по сравнению с очень дешевыми операциями с словарями велико.

Если, с другой стороны, вы измеряете время, необходимое для фактического импорта модуля, вы не видите никакой разницы между этими двумя подходами, поскольку в обоих случаях на этот раз полностью доминирует фактический импорт, а различия, возникающие с помощью словаря имен, становятся небрежными. Мы можем принудительно создавать резервные копии, удаляя модуль с sys.modules на каждой итерации:

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