`matplotlib._logged_cached` 是如何工作的?

How does `matplotlib._logged_cached` work?

以下代码来自Matplotlib的__init__文件:

def _logged_cached(fmt, func=None):
    """
    Decorator that logs a function's return value, and memoizes that value.

    After ::

        @_logged_cached(fmt)
        def func(): ...

    the first call to *func* will log its return value at the DEBUG level using
    %-format string *fmt*, and memoize it; later calls to *func* will directly
    return that value.
    """
    if func is None:  # Return the actual decorator.
        return functools.partial(_logged_cached, fmt)

    called = False
    ret = None

    @functools.wraps(func)
    def wrapper(**kwargs):
        nonlocal called, ret
        if not called:
            ret = func(**kwargs)
            called = True
            _log.debug(fmt, ret)
        return ret

    return wrapper

问题:上述函数中的缓存是如何工作的? _logged_cached 的局部变量不会在每次调用时重新初始化吗?

据我了解,局部变量在函数 returns 之后被删除(对吧?)。如果是这样,那么缓存将不起作用。然而,文档说装饰函数将在每次调用时 return 相同的对象——它确实如此。怎么样?

缓存起作用的原因是因为一个叫做闭包的概念。您的理解是正确的,局部变量在函数退出后被删除,但此处用于缓存的变量不是内部函数的局部变量。

如果您在函数上使用 _logged_cached 装饰器,该函数实质上将被嵌套函数 wrapper 替换。这个 wrapper 函数在返回时变成一个闭包(一个函数对象),它将记住封闭范围内的非局部变量。所以它本质上将有一个记住变量 calledret 的映射。每次调用函数对象(修饰函数)时,内部范围将有 calledret 可供使用。

通常它们只能在内部函数中用作只读,但通过使用关键字 nonlocal,内部函数实际上能够 change/update 变量的值 calledret 在闭包中。因此,我们之前提到的保存在函数对象中的映射会更新其值,并且每次对装饰函数的后续调用都将使用这些相同的值。