比较:import 语句与 __import__ 函数
Comparison: import statement vs __import__ function
作为问题 的跟进,我进行了一些测试,并得出了令人惊讶的结果。
我在这里比较经典 import
语句和调用 __import__
内置函数的执行时间。
为此,我在交互模式下使用以下脚本:
import timeit
def test(module):
t1 = timeit.timeit("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
与链接问题一样,这里是导入 sys
以及其他一些标准模块时的比较:
>>> test('sys')
import statement: 0.319865173171288
__import__ function: 0.38428380458522987
t(statement) < t(function)
>>> test('math')
import statement: 0.10262547545597034
__import__ function: 0.16307580163101054
t(statement) < t(function)
>>> test('os')
import statement: 0.10251490255312312
__import__ function: 0.16240755669640627
t(statement) < t(function)
>>> test('threading')
import statement: 0.11349136644972191
__import__ function: 0.1673617034957573
t(statement) < t(function)
到目前为止一切顺利,import
比 __import__()
快。
这对我来说很有意义,因为正如我在链接 post 中所写,我发现 IMPORT_NAME
指令与 CALL_FUNCTION
相比是合乎逻辑的,当后者导致调用__import__
.
但是当涉及到不太标准的模块时,结果相反:
>>> test('numpy')
import statement: 0.18907936340054476
__import__ function: 0.15840019037769792
t(statement) > t(function)
>>> test('tkinter')
import statement: 0.3798560809537861
__import__ function: 0.15899962771786136
t(statement) > t(function)
>>> test("pygame")
import statement: 0.6624641952621317
__import__ function: 0.16268579177259568
t(statement) > t(function)
这种执行时间差异背后的原因是什么?
import
语句在标准模块上更快的真正原因是什么?
另一方面,为什么 __import__
函数与其他模块一起运行速度更快?
测试领先 Python 3.6
请记住,所有模块在第一次导入后都会缓存到 sys.modules
,所以时间...
无论如何,我的结果是这样的:
#!/bin/bash
itest() {
echo -n "import : "
python3 -m timeit "import "
echo -n "__import__(''): "
python3 -m timeit "__import__('')"
}
itest "sys"
itest "math"
itest "six"
itest "PIL"
import sys
: 0.481
__import__('sys')
: 0.586
import math
: 0.163
__import__('math')
: 0.247
import six
: 0.157
__import__('six')
: 0.273
import PIL
: 0.162
__import__('PIL')
: 0.265
timeit
测量总执行时间,但是模块的第一次导入,无论是通过 import
还是 __import__
,都比后续的要慢 - 因为它是唯一一个实际上执行模块初始化。它必须在文件系统中搜索模块的文件,加载模块的源代码(最慢)或以前创建的字节码(慢但比解析 .py
文件快一点)或共享库(对于 C 扩展) ,执行初始化代码,将模块对象存入sys.modules
。随后的导入将跳过所有这些并从 sys.modules
.
检索模块对象
如果您颠倒顺序,结果会有所不同:
import timeit
def test(module):
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
test('numpy')
import statement: 0.4611093703134608
__import__ function: 1.275512785926014
t(statement) < t(function)
获得无偏差结果的最佳方法是导入一次,然后进行计时:
import timeit
def test(module):
exec("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
test('numpy')
import statement: 0.4826306561727307
__import__ function: 0.9192819125911029
t(statement) < t(function)
所以,是的,import
总是比 __import__
快。
What is the reason behind this difference in the execution times?
import 语句有一个非常简单的路径可以通过。它导致 IMPORT_NAME
调用 import_name
并导入给定的模块(如果没有覆盖名称 __import__
):
dis('import math')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (math)
6 STORE_NAME 0 (math)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
另一方面,__import__
执行所有函数通过 CALL_FUNCTION
:
执行的通用函数调用步骤
dis('__import__(math)')
1 0 LOAD_NAME 0 (__import__)
2 LOAD_NAME 1 (math)
4 CALL_FUNCTION 1
6 RETURN_VALUE
当然,它是内置的,比普通的 py 函数快得多,但它仍然比带有 import_name
的 import
语句慢。
这就是为什么它们之间的时间差是恒定的。使用@MSeifert 代码片段(纠正了不公正的时间 :-) 并添加了另一个打印件,您可以看到:
import timeit
def test(module):
exec("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
print('Diff: {}'.format(t2-t1))
for m in sys.builtin_module_names:
test(m)
在我的机器上,它们之间存在大约 0.17 的恒定差异(通常预期会有轻微差异)
*值得注意的是,这些并不完全等同。 __import__
不进行任何名称绑定,字节码证明了这一点。
作为问题
我在这里比较经典 import
语句和调用 __import__
内置函数的执行时间。
为此,我在交互模式下使用以下脚本:
import timeit
def test(module):
t1 = timeit.timeit("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
与链接问题一样,这里是导入 sys
以及其他一些标准模块时的比较:
>>> test('sys')
import statement: 0.319865173171288
__import__ function: 0.38428380458522987
t(statement) < t(function)
>>> test('math')
import statement: 0.10262547545597034
__import__ function: 0.16307580163101054
t(statement) < t(function)
>>> test('os')
import statement: 0.10251490255312312
__import__ function: 0.16240755669640627
t(statement) < t(function)
>>> test('threading')
import statement: 0.11349136644972191
__import__ function: 0.1673617034957573
t(statement) < t(function)
到目前为止一切顺利,import
比 __import__()
快。
这对我来说很有意义,因为正如我在链接 post 中所写,我发现 IMPORT_NAME
指令与 CALL_FUNCTION
相比是合乎逻辑的,当后者导致调用__import__
.
但是当涉及到不太标准的模块时,结果相反:
>>> test('numpy')
import statement: 0.18907936340054476
__import__ function: 0.15840019037769792
t(statement) > t(function)
>>> test('tkinter')
import statement: 0.3798560809537861
__import__ function: 0.15899962771786136
t(statement) > t(function)
>>> test("pygame")
import statement: 0.6624641952621317
__import__ function: 0.16268579177259568
t(statement) > t(function)
这种执行时间差异背后的原因是什么?
import
语句在标准模块上更快的真正原因是什么?
另一方面,为什么 __import__
函数与其他模块一起运行速度更快?
测试领先 Python 3.6
请记住,所有模块在第一次导入后都会缓存到 sys.modules
,所以时间...
无论如何,我的结果是这样的:
#!/bin/bash
itest() {
echo -n "import : "
python3 -m timeit "import "
echo -n "__import__(''): "
python3 -m timeit "__import__('')"
}
itest "sys"
itest "math"
itest "six"
itest "PIL"
import sys
: 0.481__import__('sys')
: 0.586import math
: 0.163__import__('math')
: 0.247import six
: 0.157__import__('six')
: 0.273import PIL
: 0.162__import__('PIL')
: 0.265
timeit
测量总执行时间,但是模块的第一次导入,无论是通过 import
还是 __import__
,都比后续的要慢 - 因为它是唯一一个实际上执行模块初始化。它必须在文件系统中搜索模块的文件,加载模块的源代码(最慢)或以前创建的字节码(慢但比解析 .py
文件快一点)或共享库(对于 C 扩展) ,执行初始化代码,将模块对象存入sys.modules
。随后的导入将跳过所有这些并从 sys.modules
.
如果您颠倒顺序,结果会有所不同:
import timeit
def test(module):
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
test('numpy')
import statement: 0.4611093703134608
__import__ function: 1.275512785926014
t(statement) < t(function)
获得无偏差结果的最佳方法是导入一次,然后进行计时:
import timeit
def test(module):
exec("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
test('numpy')
import statement: 0.4826306561727307
__import__ function: 0.9192819125911029
t(statement) < t(function)
所以,是的,import
总是比 __import__
快。
What is the reason behind this difference in the execution times?
import 语句有一个非常简单的路径可以通过。它导致 IMPORT_NAME
调用 import_name
并导入给定的模块(如果没有覆盖名称 __import__
):
dis('import math')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (math)
6 STORE_NAME 0 (math)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
另一方面,__import__
执行所有函数通过 CALL_FUNCTION
:
dis('__import__(math)')
1 0 LOAD_NAME 0 (__import__)
2 LOAD_NAME 1 (math)
4 CALL_FUNCTION 1
6 RETURN_VALUE
当然,它是内置的,比普通的 py 函数快得多,但它仍然比带有 import_name
的 import
语句慢。
这就是为什么它们之间的时间差是恒定的。使用@MSeifert 代码片段(纠正了不公正的时间 :-) 并添加了另一个打印件,您可以看到:
import timeit
def test(module):
exec("import {}".format(module))
t2 = timeit.timeit("{0} = __import__('{0}')".format(module))
t1 = timeit.timeit("import {}".format(module))
print("import statement: ", t1)
print("__import__ function:", t2)
print("t(statement) {} t(function)".format("<" if t1 < t2 else ">"))
print('Diff: {}'.format(t2-t1))
for m in sys.builtin_module_names:
test(m)
在我的机器上,它们之间存在大约 0.17 的恒定差异(通常预期会有轻微差异)
*值得注意的是,这些并不完全等同。 __import__
不进行任何名称绑定,字节码证明了这一点。