Python嵌套函数是copy-on-could-write吗?

Pau*_*zer 3 python nested function copy-on-write

请原谅Python爱好者一个主要的学术问题.

我对嵌套函数的成本(如果有的话)感兴趣 - 不是使用闭包等的功能合理的函数,而是保持外部名称空间整齐.

所以我做了一个简单的测量:

def inner(x):
    return x*x

def flat(x):
    return inner(x)

def nested(x):
    def inner(x):
        return x*x
    return inner(x)

# just to get a feel of the cost of having two more lines
def fake_nested(x):
    y = x
    z = x
    return inner(x)

from timeit import timeit

print(timeit('f(3)', globals=dict(f=flat)))
print(timeit('f(3)', globals=dict(f=nested)))
print(timeit('f(3)', globals=dict(f=fake_nested)))

# 0.17055258399341255
# 0.23098028398817405
# 0.19381927204085514
Run Code Online (Sandbox Code Playgroud)

所以似乎有一些开销,它似乎比通过再增加两行来解释.

但是,似乎def每次调用外部函数时都不会计算内部语句,实际上内部函数对象似乎是缓存的:

def nested(x):
    def inner(x):
        return x*x
    print(id(inner), id(inner.__code__), id(inner.__closure__))
    return inner(x)

nested(3)
x = [list(range(i)) for i in range(5000)] # create some memory pressure
nested(3)

# 139876371445960 139876372477824 8845216
# 139876371445960 139876372477824 8845216
Run Code Online (Sandbox Code Playgroud)

寻找可能会增加运行时间的其他事情我偶然发现了以下的nerdgasm:

def nested(x):
    def inner(x):
        return x*x
    print(id(inner), id(inner.__code__), id(inner.__closure__))
    return inner

nested(3)
x = [list(range(i)) for i in range(5000)] # create some memory pressure
a = nested(3)
x = [list(range(i)) for i in range(5000)] # create some memory pressure
nested(3)

# 139906265032768 139906264446704 8845216
# 139906265032768 139906264446704 8845216
# 139906264258624 139906264446704 8845216
Run Code Online (Sandbox Code Playgroud)

似乎如果Python检测到存在对缓存嵌套函数的外部引用,则它会创建一个新的函数对象.

现在 - 假设我的推理到目前为止还没有完全消失 - 我的问题是:这有什么好处?

我的第一个想法是"好的,如果用户有一个对缓存函数的引用,他们可能会搞乱它,所以最好做一个干净的新的." 但是在第二个想法似乎没有洗,因为副本不是一个深层副本,如果用户混淆该功能然后抛出参考怎么办?

补充问题:Python是否会在幕后做任何其他非常聪明的事情?这是否与嵌套执行速度较慢有关?

Ant*_*ala 5

你的推理完全没了.每次def在正常程序流中遇到a 时,Python总是创建一个新的函数对象- 没有例外.

它只是在CPython中id新创建的功能可能是一样的,老的.请参阅CPython中的"为什么id({})== id({})和id([])== id([])?" .

现在,如果保存了对内部函数的引用,则在创建下一个函数之前不会删除它,当然新函数不能在同一个内存地址中共存.