用装饰函数装饰类的实例方法

2 投票
2 回答
40 浏览
提问于 2025-04-13 21:00

我正在使用Python 3.10。这里有一个简单的例子,展示了一个缓存的用法,它会缓存第一次调用实例方法的结果,然后在后续调用时返回这个缓存的值:

import functools

def cache(func):
    @functools.wraps(func)  # for __name__
    def wrapper(*args, **kwargs):
        if not wrapper.cache:
            print("caching...")
            wrapper.cache = func(*args, **kwargs)
        return wrapper.cache
    wrapper.cache = None
    return wrapper

class Power:
    def __init__(self, exponent):
        self.exponent = exponent
    @cache
    def of(self, base):
        return base ** self.exponent

# test
>>> cube = Power(3)
>>> cube.of(2)
caching...
8
>>> cube.of.cache
8
>>> cube.of.__dict__
{'__wrapped__': <function __main__.Power.of(self, base)>, 'cache': 8}
>>> cube.of.cache = None
...
AttributeError: 'method' object has no attribute 'cache'

我有两个问题:

1.) 这里的一个被接受的答案提到,说这个@cache装饰器在Power类被构造时就会运行,并且它会接收到一个未绑定的方法(在我的例子中是of)。我觉得这个说法只有在你用类装饰器装饰实例方法时才成立。在这种情况下,你需要在构造装饰类的实例时存储cube对象的引用,但这个cube实例还没有定义。我很难理解这个说法和我的例子能够正常工作的事实之间的关系;被装饰的of方法接收到的参数是一个元组,第一个元素是cube实例,第二个元素是base=2参数。

2.) 我可以访问.cache属性,但为什么我不能重置它呢?这会导致AttributeError错误。

2 个回答

2

因为第二个问题比较简单,所以我先从这个开始讲。

其实在方法的文档中已经直接回答了这个问题:

就像函数对象一样,绑定的方法对象也支持获取任意属性。不过,由于方法的属性实际上是存储在底层的函数对象中(method.__func__),所以不允许在绑定的方法上设置方法属性

这一点可以很容易验证,

cube.of.__func__.cache = None

确实可以用你的例子来证明。

至于第一个问题,你并不需要一个实例就能运行装饰器——“调用装饰器”并不是指调用被装饰的函数。它指的是将这个函数包裹上额外的功能,然后返回一个新的函数来替代原来的函数。只有当你真正调用被装饰的函数时,这个过程才会被执行,这时参数需要被定义,并最终传递给of(如果缓存已经定义,则可以省略)。

2

发生的事情是,装饰器机制把cache(of)的结果赋值给了Power.of。所以其实是Power这个对象有了一个叫of的属性(也就是你的缓存装饰器)。

你可以这样写:Power.of.cache = None,这样就能得到你想要的结果:

cube = Power(3)
print(cube.of(2))
print(cube.of(2))

Power.of.cache = None
print(cube.of(2))

输出结果:

caching...
8
8
caching...
8

撰写回答