我知道 Python 中的函数是一等公民,这意味着它们是
function
类的对象,类似于 5
是 int
类的对象。这意味着在它们生命周期的某个时刻会调用构造函数。对于大多数函数,我希望在定义它们时会发生这种情况(因为大多数函数大概只定义一次),这样无论我们使用多少次,我们只支付一次构建价格。
但是嵌套函数呢?每次调用它们的父级时,它们都会被重新定义。这是否意味着我们每次都会重新构造该对象?如果是的话,那不是效率很低吗?私有方法(假设父函数是一个方法)或另一个函数不是更高效吗?我忽略了支持嵌套讨论的范围论点。
我进行了一个简单的实验,似乎支持我上述的论点,因为内部函数版本较慢:
import time
def f(x, y):
def defined_inside(x, y):
return x + y
return defined_inside(x, y)
def defined_outside(x, y):
return x + y
def g(x, y):
return defined_outside(x, y)
start = time.time()
for i in range(10000000):
_ = f(3, 4)
end = time.time()
print("Using inner function it took {} seconds".format(end - start))
start = time.time()
for i in range(10000000):
_ = g(3, 4)
end = time.time()
print("Using outer function it took {} seconds".format(end - start))
结果:
Using inner function it took 2.494696855545044 seconds
Using outer function it took 1.8862690925598145 seconds
额外问题:如果上述情况属实,这种情况与编译语言(例如 Scala)有何关系?我对嵌套和高阶函数产生了巨大的兴趣,如果这个技巧像看起来那样效率低下,那将是可怕的。
您可以使用装饰器轻松测试它:
from collections.abc import Callable
def wrapper(fun: Callable[[], None]) -> Callable[[], None]:
print(f"wrap {fun}")
def wrap() -> None:
print(f"calling {fun}")
fun()
return wrap
def foo() -> None:
@wrapper
def inner() -> None:
print("inner")
inner()
foo()
foo()
输出将是:
wrap <function foo.<locals>.inner at 0x1051fbe20>
calling <function foo.<locals>.inner at 0x1051fbe20>
inner
wrap <function foo.<locals>.inner at 0x1051fbec0>
calling <function foo.<locals>.inner at 0x1051fbec0>
inner
每次调用
foo
时,都会重新定义函数inner
(上面,每个版本都有自己的地址)。这是有道理的,因为您可能想在内部函数中捕获局部变量等。