在CPython中使用AFAIK,函数定义在解析时执行时被编译成函数对象。但内在功能呢?它们是在解析时编译到函数对象中还是在每次调用函数时都被编译(或解释)?内部功能是否会产生任何性能损失?
答案 0 :(得分:30)
给出一般性解释 - 假设您在模块中有以下代码:
def outer(x=1):
def inner(y=2):
return x+y
当文件由python通过compile()
解析时,上述文本将变为字节码,以便如何执行模块。在模块字节码中,有两个“代码对象”,一个用于outer()
的字节码,另一个用于字节码inner()
。请注意,我说的是代码对象,而不是函数 - 代码对象只包含函数使用的字节码,以及编译时可以知道的任何信息 - 例如包含引用的outer()
的字节码inner()
的字节码。
当模块实际加载时,通过评估与模块关联的代码对象,发生的一件事是为outer()
创建一个实际的“函数对象”,并存储在模块的outer
属性中。函数对象充当字节码的集合以及调用函数所需的所有上下文相关事物(例如,哪些全局变量应该从中拉出等),这在编译时是不可知的。在某种程度上,代码对象是函数的模板,它是用于执行填充了所有变量的实际字节码的模板。
这些都不涉及inner()
- 作为函数 - 每次实际调用outer()
时,都会创建一个新的inner()
函数对象对于外部的调用,它将已经创建的内部字节码对象绑定到全局变量列表,包括传递给外部调用的x
的值。你可以想象,这是非常快的,因为不需要解析,只需用一些指向其他现有对象的快速结构填充。
答案 1 :(得分:9)
简单测试:函数的默认参数在定义时调用一次。
>>> def foo():
... def bar(arg=count()):
... pass
... pass
...
>>> def count():
... print "defined"
...
>>> foo()
defined
>>> foo()
defined
所以是的:这是次要的(非常非常小的)性能影响。
答案 2 :(得分: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
因此我们可以得出结论,它们已经编译完毕。至于它们的性能特征,请使用它们。如果您开始遇到性能问题,请进行个人资我知道这不是一个真正的答案,但它几乎永远不会重要,而当它确实如此时,一般的答案并没有削减它。函数调用产生一些开销,看起来内部函数就像函数一样。
答案 3 :(得分:2)
要扩展nmichaels,回答内部函数在 compile 时编译,因为他猜到并且字节代码保存在foo.func_code.co_consts
中,并且使用操作码 LOAD_CONST 正如您在函数的反汇编中所看到的那样。
示例:
>>> def foo():
... def inner():
... pass
>>> print foo.func_code.co_consts
(None, <code object inner at 0x249c6c0, file "<ipython console>", line 2>)
答案 4 :(得分:1)
我迟到了,但作为对这些彻底答案的一点实验性补充:您可以使用builtin function id
来验证是否创建了新对象:
In []: # inner version
def foo():
def bar():
return id(bar)
return bar()
foo(), foo()
Out[]: (4352951432, 4352952752)
实际数字可能有所不同,但它们的差异表明确实创建了两个不同的bar
实例。
In []: # outer version
def bar():
return id(bar)
def foo():
return bar()
foo(), foo()
Out[]: (4352950952, 4352950952)
这一次,正如预期的那样,两个id
是相同的。
现在进行一些timeit
测量。内在第一,外在第二:
100000 loops, best of 3: 1.93 µs per loop
1000000 loops, best of 3: 1.25 µs per loop
所以,在我的机器上,似乎内部版本慢了50%(Python 2.7,IPython Notebook)。