Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/297.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么从一个模块导入函数比从整个模块本身导入函数要花更长的时间?_Python_Performance_Python Import_Python Internals - Fatal编程技术网

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

Python 为什么从一个模块导入函数比从整个模块本身导入函数要花更长的时间?,python,performance,python-import,python-internals,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 仅导入分派功能而不是导入整个模块所需的时间要长得多,这似乎违反了直觉。有人能解释一下为什么执行单个函数的开销如此

考虑:

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

仅导入分派功能而不是导入整个模块所需的时间要长得多,这似乎违反了直觉。有人能解释一下为什么执行单个函数的开销如此之大吗?谢谢

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

这是因为:

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

但是,win32com.client导入调度的
有其自身的优点,例如,如果在代码中多次使用
win32com.client.Dispatch
,则最好将其分配给变量,以便减少查找次数。否则,每次调用
win32com.client.Dispatch()
将首先搜索
win32com
,然后在
win32com
内部搜索
client
,最后在
win32com.client
内部搜索
Dispatch


字节码比较:

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
从字节码可以明显看出,从os.path导入splitext所需的
步骤数大于简单的
导入所需的步骤数

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

模块缓存:

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
请注意,在从os.path导入splitext
之后,仍然可以使用
sys.modules
访问
os
模块,因为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
输出:

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

这里的主要问题是,您的代码没有按照您认为的时间进行计时
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

我想接下来的问题是,整个模块都可以缓存,但python是否也可以从模块中捕获函数?@JonClements:不仅可以解析,还可以导入。在从foo导入栏查看
之后,查看
sys.modules['foo']
,您将看到与执行
import foo
@理论完全相同的操作:您的评论是什么意思?函数只是一个具有几个属性的对象,其中一个属性是字节码。没有必要将其缓存;这只是一个你可以像其他任何东西一样复制的东西。可以缓存名称,但是…到哪里?它已经在
globals()
中了,所以将它缓存到
globals()
不会有什么帮助…@abarnert谢谢-不太清楚我为什么要写解析稿-我的原始草稿已经导入了-这就是它迟到时发生的情况;)@乔克莱门茨:别担心;无论如何,我认为我们都正确地导入了您的答案。:)我更喜欢这个答案!这是一个答案——包括:概念、实践证明和doc标准。任何人都能得到最好的答案。我认为这个答案没有切中要害。OP假设导入语句的执行时间由实际导入模块决定。这个假设通常是正确的,但是OP这次的测量方法是错误的,这个答案详细说明了为什么你用这种方法测量时会得到观察到的结果。更重要的一点是,OP没有测量他认为他正在测量的内容,这一点是缺失的。@SvenMarnach使用timeit的命令行界面对单个循环进行了测试,正常导入速度更快。只有一个循环,timeit的结果非常嘈杂且毫无意义。试着运行几次——我很确定这是不可预测的。此外,您的回答无法解释1µs的时间差——在现代机器上,很少的字典操作只需要几纳秒。我刚刚意识到,仅第一次导入实际上也会执行导入,这使得我问题中的示例有些误导。感谢您指出这是如何导致时间大致相同的。