比较:import语句与__import__函数

时间:2017-09-12 12:03:10

标签: python performance python-import

作为Using builtin __import__() in normal cases问题的后续内容,我进行了一些测试,结果令人惊讶。

我在这里比较经典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__()更快。 这对我来说很有意义,因为正如我在链接帖子中所写的那样,我发现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

测试

3 个答案:

答案 0 :(得分:12)

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__快。

答案 1 :(得分:4)

请记住,所有模块在第一次导入后都会缓存到sys.modules,所以时间......

无论如何,我的结果看起来像这样:

#!/bin/bash

itest() {
    echo -n "import $1: "
    python3 -m timeit "import $1"
    echo -n "__import__('$1'): "
    python3 -m timeit "__import__('$1')"
}

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

enter image description here

答案 2 :(得分:3)

  

执行时间差异背后的原因是什么?

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语句慢import_name

这就是为什么它们之间的时间差是不变的。使用@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__没有任何名称绑定,因为字节码证明了这一点。