Python内部函数是编译的吗?

28 投票
5 回答
2129 浏览
提问于 2025-04-16 17:44

据我所知,在CPython中,函数定义在解析时会被编译成函数对象。那么,内部函数呢?它们是在解析时就被编译成函数对象,还是每次调用这个函数时才编译(或者解释)一次?内部函数会不会影响性能呢?

5 个回答

8
>>> import dis
>>> def foo():
...     def bar():
...             print "stuff"
...     return bar
... 
>>> b = foo()
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x20bf738, file "<stdin>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE        
>>> dis.dis(b)
  3           0 LOAD_CONST               1 ('stuff')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        

我怀疑这个问题很大程度上取决于具体的实现,但这是在 CPython 2.6.6 版本下的情况,里面的函数看起来是被编译过的。这里还有另一个例子:

>>> def foo():
...     def bar():
...             return 1
...     return dis.dis(bar)
... 
>>> foo()
  3           0 LOAD_CONST               1 (1)
              3 RETURN_VALUE

所以我们可以得出结论,它们是被编译的。至于它们的性能特点,直接用就行。如果你开始遇到性能问题,那就进行性能分析。我知道这并不是一个真正的答案,但这几乎从来不重要,而当它重要时,通用的答案也不够用。函数调用会有一些额外的开销,而内部函数看起来和普通函数一样。

10

简单测试:函数的默认参数在定义的时候只会被调用一次。

>>> def foo():
...     def bar(arg=count()):
...             pass
...     pass
...
>>> def count():
...     print "defined"
...
>>> foo()
defined
>>> foo()
defined

所以没错:这确实会有一点点(非常非常小的!)性能影响。

39

简单来说,假设你在一个模块里有以下代码:

def outer(x=1):
    def inner(y=2):
        return x+y

当这个文件通过 compile() 被 Python 解析时,上面的代码会变成字节码,这些字节码告诉 Python 如何执行这个 模块。在这个模块的字节码中,有两个“代码对象”,一个是 outer() 的字节码,另一个是 inner() 的字节码。注意,我说的是代码对象,而不是函数——代码对象里包含的内容不多,主要就是函数使用的字节码,还有一些在编译时就能知道的信息,比如 outer() 的字节码里会有指向 inner() 字节码的引用。

当模块真正加载时,Python 会评估与模块相关的代码对象,这时会创建一个实际的“函数对象”给 outer(),并把它存储在模块的 outer 属性里。这个函数对象就像是一个集合,里面有字节码和调用这个函数所需的所有上下文信息(例如,它应该从哪个全局字典中获取数据等),这些信息在编译时是无法知道的。可以说,代码对象是函数的模板,而函数对象则是执行实际字节码的模板,所有变量都已经填充好。

到目前为止,这一切都还没有涉及到 inner() 作为一个函数的部分——每次你真正调用 outer() 时,才会为那次调用创建一个新的 inner() 函数对象,这个对象会把已经创建的 inner 字节码对象与一个全局变量列表绑定在一起,包括传递给 outer()x 的值。可以想象,这个过程非常快,因为不需要重新解析,只需快速填充一个结构,指向一些已经存在的对象。

撰写回答