Python内部函数是编译的吗?
据我所知,在CPython中,函数定义在解析时会被编译成函数对象。那么,内部函数呢?它们是在解析时就被编译成函数对象,还是每次调用这个函数时才编译(或者解释)一次?内部函数会不会影响性能呢?
5 个回答
>>> 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
所以我们可以得出结论,它们是被编译的。至于它们的性能特点,直接用就行。如果你开始遇到性能问题,那就进行性能分析。我知道这并不是一个真正的答案,但这几乎从来不重要,而当它重要时,通用的答案也不够用。函数调用会有一些额外的开销,而内部函数看起来和普通函数一样。
简单测试:函数的默认参数在定义的时候只会被调用一次。
>>> def foo():
... def bar(arg=count()):
... pass
... pass
...
>>> def count():
... print "defined"
...
>>> foo()
defined
>>> foo()
defined
所以没错:这确实会有一点点(非常非常小的!)性能影响。
简单来说,假设你在一个模块里有以下代码:
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
的值。可以想象,这个过程非常快,因为不需要重新解析,只需快速填充一个结构,指向一些已经存在的对象。