Python 在函数级别导入与模块级别导入
当我不确定的时候,我通常会把导入的语句放在模块的最上面。这样做通常可以减少重复,这样挺好的。不过,如果只有一个函数(或类)需要这个导入,这样做会不会影响性能呢?
下面的代码只有在函数被调用时才会导入吗?
def func():
from task import test
如果是这样的话,我想这可能会稍微提高效率。我还认为,这样做可能会让垃圾回收和变量作用域更快,因为导入的对象不会被加入到全局字典里。正如另一位网友所说:
这主要是因为变量查找。在全局作用域中查找变量需要查字典。相比之下,编译器会静态地确定局部名称,并通过索引引用它们,所以不需要查字典。
这些假设合理吗,还是我完全想错了?
谢谢
3 个回答
我觉得如果这个导入的内容不常用,把它放在定义里面是有道理的。
让我们看看下面这两个函数的字节码会是什么样子:
def func1():
""" test imported each time function is run """
from task import test
test()
def func2():
""" test was imported at top of module """
test()
正如你在下面看到的,func2()
通过使用全局导入的test
函数,省去了很多步骤。
>>> dis.dis(func1)
3 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('test',))
6 IMPORT_NAME 0 (task)
9 IMPORT_FROM 1 (test)
12 STORE_FAST 0 (test)
15 POP_TOP
4 16 LOAD_FAST 0 (test)
19 CALL_FUNCTION 0
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(func2)
3 0 LOAD_GLOBAL 0 (test)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 1 (None)
10 RETURN_VALUE
提前考虑这些问题可能是过早的优化,正如delnan在评论中提到的。
至于test
在全局命名空间中,这不太可能导致查找性能问题。我认为最明显的情况是,如果test
和你经常使用的另一个名字发生了哈希冲突,这可能会导致查找那个第二个名字的时间变长。不过,再次强调,考虑这种罕见情况也是一种过早的优化。
在一个函数里面的导入,只有在这个函数运行的时候才会被导入。要记住,在Python中,所有的语句都是在遇到的时候执行的,导入语句和其他语句一样。顶层的导入是在模块被导入的时候就已经导入了,因为它们是在模块中的顶层语句。
你对名称查找的担忧其实是多余的:这个差别几乎可以忽略不计,只有在性能分析显示有问题的时候才需要考虑。
我只在函数范围内导入模块有两个原因:1)解决循环导入的问题,顺便说一下,这种问题通常可以通过重构来解决;或者2)如果这个模块是可选的,并且这个函数不是很多用户使用的。在第二种情况下,这个模块可以完全缺失,除非有人调用这个函数,否则不会有问题。