Python 导入 X 或从 X 导入 Y?(性能)

42 投票
4 回答
16371 浏览
提问于 2025-04-16 03:26

如果我打算使用一个库里的至少两个方法,那么下面这两种写法在性能或内存使用上有什么区别吗?

from X import method1, method2

import X

4 个回答

10

如果你在一个循环里调用一个函数很多次(比如上百万次),这就很重要了。每次进行双重字典查找都会累积起来,最终可能会影响性能。下面的例子显示了大约20%的性能提升。

这里提到的时间是针对在Win7 64位机器上运行的Python 3.4的。(如果你用的是Python 2.7,记得把range命令改成xrange)。

这个例子主要参考了《高性能Python》这本书,虽然他们提到的本地函数查找更快的第三个例子对我来说似乎不再适用。

import math
from math import sin

def tight_loop_slow(iterations):
    """
    >>> %timeit tight_loop_slow(10000000)
    1 loops, best of 3: 3.2 s per loop
    """
    result = 0
    for i in range(iterations):
        # this call to sin requires two dictionary lookups
        result += math.sin(i)

def tight_loop_fast(iterations):
    """
    >>> %timeit tight_loop_fast(10000000)
    1 loops, best of 3: 2.56 s per loop
    """
    result = 0
    for i in range(iterations):
        # this call to sin only requires only one lookup
        result += sin(i)
11

其实在内存和速度上没有什么区别(因为整个模块都得被计算一遍,最后一行可能是 Y = something_else)。除非你的电脑是上世纪80年代的,不然这根本不重要。

54

这两种写法是有区别的。在使用 import x 的时候,系统需要查找两次名字:第一次是查找模块的名字,第二次是查找函数的名字。而使用 from x import y 时,只需要查找一次。

你可以通过 dis 模块很清楚地看到这一点:

import random
def f_1():
    random.seed()

dis.dis(f_1)
     0 LOAD_GLOBAL              0 (random)
     3 LOAD_ATTR                0 (seed)
     6 CALL_FUNCTION            0
     9 POP_TOP
    10 LOAD_CONST               0 (None)
    13 RETURN_VALUE

from random import seed

def f_2():
    seed()

dis.dis(f_2)
     0 LOAD_GLOBAL              0 (seed)
     3 CALL_FUNCTION            0
     6 POP_TOP
     7 LOAD_CONST               0 (None)
    10 RETURN_VALUE

从上面可以看出,使用 from x import y 的速度稍微快一点。

不过,import x 的开销比 from x import y 小,因为它少了一次名字查找。我们来看一下拆解后的代码:

def f_3():
    import random

dis.dis(f_3)
     0 LOAD_CONST               1 (-1)
     3 LOAD_CONST               0 (None)
     6 IMPORT_NAME              0 (random)
     9 STORE_FAST               0 (random)
    12 LOAD_CONST               0 (None)
    15 RETURN_VALUE

def f_4():
    from random import seed

dis.dis(f_4)
     0 LOAD_CONST               1 (-1)
     3 LOAD_CONST               2 (('seed',))
     6 IMPORT_NAME              0 (random)
     9 IMPORT_FROM              1 (seed)
    12 STORE_FAST               0 (seed)
    15 POP_TOP
    16 LOAD_CONST               0 (None)
    19 RETURN_VALUE

我不知道具体原因,但似乎 from x import y 看起来像是一个函数调用,因此它的开销比预期的还要高;所以,如果你只用一次导入的函数,使用 import x 会更快,而如果你用多次,那使用 from x import y 就会更快。

不过,正如往常一样,我建议你不要仅仅根据这些知识来决定如何导入模块和函数,因为这只是一些过早的优化。
我个人觉得,在很多情况下,明确的命名空间更容易阅读,所以我建议你也这样做:用你自己的审美观来判断 :-)

撰写回答