import subprocess
和之间的性能差异from subprocess import call
可能非常小(可能完全由实际创建和运行其他进程所涉及的开销支配)。但是,它们之间存在一些小的性能差异,可能值得看看它们的来源。
这是我用来了解他们所做工作的代码。我使用该dis
模块来反汇编已编译的 Python 字节码,因此我们可以看到发生了什么:
import dis
from_import_src = """
from subprocess import call
foo = call
"""
plain_import_src = """
import subprocess
foo = subprocess.call
"""
from_bytecode = compile(from_import_src, "from_import", "exec")
plain_bytecode = compile(plain_import_src, "plain_import", "exec")
print("from ... import ...")
dis.dis(from_bytecode)
print("\nimport ...")
dis.dis(plain_bytecode)
输出:
from ... import ...
2 0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (('call',))
6 IMPORT_NAME 0 (subprocess)
9 IMPORT_FROM 1 (call)
12 STORE_NAME 1 (call)
15 POP_TOP
3 16 LOAD_NAME 1 (call)
19 STORE_NAME 2 (foo)
22 LOAD_CONST 2 (None)
25 RETURN_VALUE
import ...
2 0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (subprocess)
9 STORE_NAME 0 (subprocess)
3 12 LOAD_NAME 0 (subprocess)
15 LOAD_ATTR 1 (call)
18 STORE_NAME 2 (foo)
21 LOAD_CONST 1 (None)
24 RETURN_VALUE
如果您不熟悉dis.dis
输出,这里有一个简要概述: 第一列是源代码行(因为我们的源代码的第一行是空白的,所以真正的代码在第 2 行和第 3 行)。第三列显示要运行的字节码指令,最后一列显示它接收的参数。其他数字并不重要。
现在,并非每个 Python 字节码都需要相同的时间来执行,但作为第一个近似值,比较两个代码的每一行的指令数可以表明它们在性能上的不同之处。
第一个版本from ... import ...
在导入步骤中使用了更多的工作。也就是说,它首先导入模块,然后call
从中加载属性并将其保存到本地命名空间中。call
尽管在我们访问函数以将其保存到另一个变量中的第二步中,这一前期成本得到了回报。只需要一次查找,因为call
它已经在顶级命名空间中。
使用的第二个版本import ...
在导入行上做的前期工作较少,但它必须进行两次查找才能subprocess.call
在第二行中解析。
因此,我们可以得出结论,如果您多次访问一个属性,该from ... import ...
方法可能会更快。另一方面,如果您只访问该属性一次,则使用纯文本import ...
可能会降低开销。
但实际上,进口的性能可能并不是一件很重要的事情。使用使您的代码最易读的任何东西,并且只有在分析告诉您存在重大问题时才担心性能。