在Python中,如果我在父函数中有一个子函数,那么每次调用父函数时,子函数是否“初始化”(创建)?将函数嵌套在另一个函数中是否有任何开销?
在Python中,如果我在父函数中有一个子函数,那么每次调用父函数时,子函数是否“初始化”(创建)?将函数嵌套在另一个函数中是否有任何开销?
Answers:
是的,每次都会创建一个新对象。除非您处于紧密的循环中,否则这可能不是问题。分析将告诉您是否有问题。
In [80]: def foo():
....: def bar():
....: pass
....: return bar
....:
In [81]: id(foo())
Out[81]: 29654024
In [82]: id(foo())
Out[82]: 29651384
bar()
因为第一个立即被垃圾收集。尝试a = foo(); b = foo()
比较ID(它们会有所不同)。有关相关说明,请参见stackoverflow.com/questions/2906177/…。
代码对象是预编译的,因此该部分没有任何开销。函数对象在每次调用时构建-将函数名称绑定到代码对象,记录默认变量等。
内容提要:它不是免费的。
>>> from dis import dis
>>> def foo():
def bar():
pass
return bar
>>> dis(foo)
2 0 LOAD_CONST 1 (<code object bar at 0x1017e2b30, file "<pyshell#5>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (bar)
4 9 LOAD_FAST 0 (bar)
12 RETURN_VALUE
有影响,但是在大多数情况下,它是如此之小,以至您不必担心-大多数非平凡的应用程序可能已经具有性能瓶颈,其影响比此影响大几个数量级。而是担心代码的可读性和可重用性。
这里的一些代码比较每次通过循环重新定义功能与重新使用预定义功能的性能。
import gc
from datetime import datetime
class StopWatch:
def __init__(self, name):
self.name = name
def __enter__(self):
gc.collect()
self.start = datetime.now()
def __exit__(self, type, value, traceback):
elapsed = datetime.now()-self.start
print '** Test "%s" took %s **' % (self.name, elapsed)
def foo():
def bar():
pass
return bar
def bar2():
pass
def foo2():
return bar2
num_iterations = 1000000
with StopWatch('FunctionDefinedEachTime') as sw:
result_foo = [foo() for i in range(num_iterations)]
with StopWatch('FunctionDefinedOnce') as sw:
result_foo2 = [foo2() for i in range(num_iterations)]
当我在运行OS X Lion的Macbook Air上以Python 2.7运行此代码时,我得到:
** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **
我也对此感到很好奇,所以我决定弄清楚这会产生多少开销。TL; DR,答案不多。
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> def subfunc():
... pass
...
>>> def no_inner():
... return subfunc()
...
>>> def with_inner():
... def s():
... pass
... return s()
...
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
0.22971350199986773
>>> timeit('[with_inner() for _ in range(1000000)]', setup='from __main__ import with_inner', number=1)
0.2847519510000893
我的直觉是查看百分比(with_inner慢了24%),但是在这种情况下,这个数字会产生误导,因为我们永远不会仅仅从外部函数返回内部函数的值,尤其是对于那些不会实际上什么都做。
犯了这个错误之后,我决定将其与其他常见问题进行比较,以查看何时该发生或不发生:
>>> def no_inner():
... a = {}
... return subfunc()
...
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
0.3099582109998664
看到这一点,我们可以看到比创建一个空的dict(快速的方法)要花费更少的时间,因此,如果您做的事情很简单,那么这可能根本没有关系。
其他答案很好,并且确实很好地回答了这个问题。我想补充一点,在Python中使用for循环,生成函数等可以避免大多数内部函数。
考虑以下示例:
def foo():
# I need to execute a function on two sets of arguments:
argSet1 = (1, 3, 5, 7)
argSet2 = (2, 4, 6, 8)
# A Function could be executed on each set of args
def bar(arg1, arg2, arg3, arg4):
return (arg1 + arg2 + arg3 + arg4)
total = 0
for argSet in [argSet1, argSet2]:
total += bar(*argSet)
print( total )
# Or a loop could be used on the argument sets
total = 0
for arg1, arg2, arg3, arg4 in [argSet1, argSet2]:
total += arg1 + arg2 + arg3 + arg4
print( total )
这个例子有点愚蠢,但我希望您仍然可以理解我的观点。通常不需要内部功能。
是。这将启用闭包以及功能工厂。
闭包会导致内部函数在调用时记住其环境的状态。
def generate_power(number):
# Define the inner function ...
def nth_power(power):
return number ** power
return nth_power
例
>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)
>>> print(raise_two(3))
8
>>> print(raise_three(5))
243
"""