25

考虑:

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

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

4

3 回答 3

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()都会先搜索search win32com,然后clientinside win32com,最后Dispatchinside 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 缓存了导入的模块。

来自文档

注意 出于效率原因,每个模块在每个解释器会话中只导入一次。因此,如果你改变你的模块,你必须重新启动解释器——或者,如果它只是你想要交互测试的一个模块,使用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
于 2013-07-12T00:21:49.037 回答
11

仍然必须导入整个模块才能从中获取所需的名称......您还会发现操作系统正在缓存模块,因此后续访问.pyc文件会更快。

于 2013-07-12T00:19:48.627 回答
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
于 2013-07-12T19:18:41.493 回答