为什么从模块导入函数比整个模块本身需要更长的时间?

时间:2013-07-12 00:16:59

标签: python performance python-import python-internals

考虑:

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

仅导入Dispatch函数而不是整个模块需要更长的时间,这似乎是反直觉的。有人可以解释为什么单一功能的开销是如此糟糕?谢谢!

3 个答案:

答案 0 :(得分: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

模块缓存:

请注意,在os之后,您仍然可以使用sys.modules访问reload()模块,因为python会缓存导入的模块。

来自docs

  

注意出于效率原因,每个模块仅导入一次   口译员会议。因此,如果您更改模块,则必须   重新启动解释器 - 或者,如果它只是您要测试的一个模块   以交互方式,使用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}}

答案 1 :(得分:11)

仍然需要导入整个模块以从中获取您想要的名称...您还会发现操作系统正在缓存模块,因此后续访问.pyc文件会更快。< / p>

答案 2 :(得分:2)

这里的主要问题是你的代码没有按你认为的时间计时。 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
相关问题