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

The*_*CAL 25 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
Run Code Online (Sandbox Code Playgroud)

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

Ash*_*ary 33

那是因为:

from win32com.client import Dispatch
Run Code Online (Sandbox Code Playgroud)

相当于:

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
Run Code Online (Sandbox Code Playgroud)

但是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     
Run Code Online (Sandbox Code Playgroud)

模块缓存:

请注意,在from os.path import splitext仍然可以访问os模块之后,sys.modules因为python会缓存导入的模块.

来自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']
Run Code Online (Sandbox Code Playgroud)

输出:

os not found
os.path is not found
<module 'os' from '/usr/lib/python2.7/os.pyc'>
Run Code Online (Sandbox Code Playgroud)

时间比较:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很棒的答案 - 包括:*概念*,*实用证明*和*doc-standard*.任何人都可以获得最佳答案. (3认同)

Jon*_*nts 11

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