Python 在函数级别导入与模块级别导入

16 投票
3 回答
12959 浏览
提问于 2025-04-17 11:14

当我不确定的时候,我通常会把导入的语句放在模块的最上面。这样做通常可以减少重复,这样挺好的。不过,如果只有一个函数(或类)需要这个导入,这样做会不会影响性能呢?

下面的代码只有在函数被调用时才会导入吗?

     def func():
         from task import test

如果是这样的话,我想这可能会稍微提高效率。我还认为,这样做可能会让垃圾回收和变量作用域更快,因为导入的对象不会被加入到全局字典里。正如另一位网友所说:

这主要是因为变量查找。在全局作用域中查找变量需要查字典。相比之下,编译器会静态地确定局部名称,并通过索引引用它们,所以不需要查字典。

这些假设合理吗,还是我完全想错了?

谢谢

3 个回答

0

我觉得如果这个导入的内容不常用,把它放在定义里面是有道理的。

5

让我们看看下面这两个函数的字节码会是什么样子:

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和你经常使用的另一个名字发生了哈希冲突,这可能会导致查找那个第二个名字的时间变长。不过,再次强调,考虑这种罕见情况也是一种过早的优化。

14

在一个函数里面的导入,只有在这个函数运行的时候才会被导入。要记住,在Python中,所有的语句都是在遇到的时候执行的,导入语句和其他语句一样。顶层的导入是在模块被导入的时候就已经导入了,因为它们是在模块中的顶层语句。

你对名称查找的担忧其实是多余的:这个差别几乎可以忽略不计,只有在性能分析显示有问题的时候才需要考虑。

我只在函数范围内导入模块有两个原因:1)解决循环导入的问题,顺便说一下,这种问题通常可以通过重构来解决;或者2)如果这个模块是可选的,并且这个函数不是很多用户使用的。在第二种情况下,这个模块可以完全缺失,除非有人调用这个函数,否则不会有问题。

撰写回答