用装饰函数装饰类的实例方法
我正在使用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 个回答
因为第二个问题比较简单,所以我先从这个开始讲。
其实在方法的文档中已经直接回答了这个问题:
就像函数对象一样,绑定的方法对象也支持获取任意属性。不过,由于方法的属性实际上是存储在底层的函数对象中(
method.__func__
),所以不允许在绑定的方法上设置方法属性。
这一点可以很容易验证,
cube.of.__func__.cache = None
确实可以用你的例子来证明。
至于第一个问题,你并不需要一个实例就能运行装饰器——“调用装饰器”并不是指调用被装饰的函数。它指的是将这个函数包裹上额外的功能,然后返回一个新的函数来替代原来的函数。只有当你真正调用被装饰的函数时,这个过程才会被执行,这时参数需要被定义,并最终传递给of
(如果缓存已经定义,则可以省略)。
发生的事情是,装饰器机制把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