数据如何在多个调饰函数调用中保持持久性?

rah*_*hmu 6 python internals decorator memoization

以下函数旨在用作存储已计算值的结果的装饰器.如果之前已经计算过该参数,该函数将返回存储在cache字典中的值:

def cached(f):
    f.cache = {}
    def _cachedf(*args):
        if args not in f.cache:
            f.cache[args] = f(*args)

        return f.cache[args]

    return _cachedf
Run Code Online (Sandbox Code Playgroud)

我意识到(错误地)cache不需要是函数对象的属性.事实上,以下代码也适用:

def cached(f):
    cache = {}   # <---- not an attribute this time!
    def _cachedf(*args):
        if args not in cache:
            cache[args] = f(*args)

        return cache[args]
    return _cachedf
Run Code Online (Sandbox Code Playgroud)

我很难理解cache对象如何在多个调用中持久化.我尝试多次调用多个缓存函数,但找不到任何冲突或问题.

任何人都可以帮助我理解cache即使在_cachedf函数返回后变量仍然存在?

Sve*_*ach 12

您在此处创建一个闭包:该函数_cachedf()关闭cache来自封闭范围的变量.cache只要函数对象存在,它就会保持活动状态.

编辑:也许我应该添加一些关于它如何在Python中工作的细节以及CPython如何实现它.

让我们看一个更简单的例子:

def f():
    a = []
    def g():
        a.append(1)
        return len(a)
    return g
Run Code Online (Sandbox Code Playgroud)

交互式解释器中的示例用法

>>> h = f()
>>> h()
1
>>> h()
2
>>> h()
3
Run Code Online (Sandbox Code Playgroud)

在编译包含该函数的模块期间f(),编译器会看到该函数g()引用了a封闭范围中的名称,并将该外部引用存储在与该函数对应的代码对象中f()(具体而言,它将名称添加af.__code__.co_cellvars).

那么在调用函数时会发生什么f()?第一行创建一个新的列表对象并将其绑定到名称a.下一行创建一个新的函数对象(使用在编译模块期间创建的代码对象)并将其绑定到名称g.此时g()不执行主体,最后返回funciton对象.

由于代码对象f()具有a本地函数引用该名称的注释,因此在f()输入时将创建此名称的"单元格" .此单元格包含对a绑定的实际列表对象的引用,并且该函数g()获取对此单元格的引用.这样,即使函数f()退出,列表对象和单元格也会保持活动状态.