这里有两个问题。第一步是找出哪个部分更快:导入语句或调用。
所以,让我们这样做:
$ python -mtimeit 'import math'
1000000 loops, best of 3: 0.555 usec per loop
$ python -mtimeit 'from math import sqrt'
1000000 loops, best of 3: 1.22 usec per loop
$ python -mtimeit -s 'from math import sqrt' 'sqrt(10)'
10000000 loops, best of 3: 0.0879 usec per loop
$ python -mtimeit -s 'import math' 'math.sqrt(10)'
10000000 loops, best of 3: 0.122 usec per loop
(这是我笔记本电脑上 OS X 10.6.4 上的 Apple CPython 2.7.2 64 位。但是同一台笔记本电脑上的 python.org 3.4 dev 和 linux 机器上的 3.3.1 给出了大致相似的结果。使用 PyPy,更智能的缓存无法进行测试,因为一切都在 1ns 内完成……无论如何,我认为这些结果可能与微基准测试一样便携。)
所以事实证明,该import
语句的速度是原来的两倍多;之后,调用函数会慢一点,但不足以弥补更便宜的import
. (请记住,您的测试是import
为每次调用执行一次。当然,在实际代码中,您往往会多次调用一次import
。所以,我们真的在寻找一种很少影响的边缘情况真正的代码。但只要你记住这一点,我们就会继续。)
从概念上讲,您可以理解为什么from … import
声明需要更长的时间:它有更多的工作要做。第一个版本必须找到模块,必要时编译并执行它。第二个版本必须完成所有这些,然后将其提取sqrt
并插入到当前模块的全局变量中。所以,它必须至少慢一点。
如果您查看字节码(例如,通过使用dis
模块并调用dis.dis('import math')
),这正是不同之处。相比:
0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (math)
9 STORE_NAME 0 (math)
12 LOAD_CONST 1 (None)
15 RETURN_VALUE
… 至:
0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (('sqrt',))
6 IMPORT_NAME 0 (math)
9 IMPORT_FROM 1 (sqrt)
12 STORE_NAME 1 (sqrt)
15 POP_TOP
16 LOAD_CONST 2 (None)
19 RETURN_VALUE
额外的堆栈操作(LOAD_CONST
and POP_TOP
)可能没有太大区别,并且使用不同的参数 toSTORE_NAME
根本不太重要……但这IMPORT_FROM
是一个重要的额外步骤。
令人惊讶的是,对代码进行快速而肮脏的尝试IMPORT_FROM
表明,大部分成本实际上是查找要导入的适当全局变量。我不知道为什么,但是……这意味着导入大量名称应该不会比只导入一个名称慢很多。而且,正如您在评论中指出的那样,这正是您所看到的。(但不要过多解读。有很多原因IMPORT_FROM
可能有一个很大的常数因子,而只有一个很小的线性因子,而且我们并没有完全给出大量的名字。)
最后一件事:如果这在实际代码中真的很重要,并且您想要两全其美,那么import math; sqrt = math.sqrt
它比from math import sqrt
. (但同样,我无法想象任何真正重要的代码。唯一一次你会关心需要多长时间sqrt
是当你调用它十亿次时,你不会关心多久导入需要。另外,如果您确实需要优化它,请创建一个本地范围并sqrt
在那里绑定以完全避免全局查找。)