我是Python修饰者的新手(哇,很棒的功能!),我无法让下面的代码工作,因为self
参数混淆了。
#this is the decorator
class cacher(object):
def __init__(self, f):
self.f = f
self.cache = {}
def __call__(self, *args):
fname = self.f.__name__
if (fname not in self.cache):
self.cache[fname] = self.f(self,*args)
else:
print "using cache"
return self.cache[fname]
class Session(p.Session):
def __init__(self, user, passw):
self.pl = p.Session(user, passw)
@cacher
def get_something(self):
print "get_something called with self = %s "% self
return self.pl.get_something()
s = Session(u,p)
s.get_something()
当我跑步时,我得到:
get_something called with self = <__main__.cacher object at 0x020870F0>
Traceback:
...
AttributeError: 'cacher' object has no attribute 'pl'
我所在的行self.cache[fname] = self.f(self,*args)
问题 - 显然,问题是self
是cacher对象而不是Session实例,它实际上没有pl
属性。但是我找不到如何解决这个问题。
我考虑过的解决方案,但不能使用 - 我想让装饰器类返回一个函数而不是一个值(就像在这个article的2.1节中那样)所以在正确的上下文中评估self
,但这是不可能的,因为我的装饰器是作为一个类实现的,并使用内置的__call__
方法。然后我想不为我的装饰器使用一个类,所以我不需要__call__method,但我不能这样做,因为我需要在装饰器调用之间保持状态(即保持跟踪self.cache
属性中的内容。
问题 - 所以,除了使用全局cache
字典变量(我没有尝试,但假设会起作用)之外,是否还有其他方法可以使这个装饰工作?
编辑:这个问题似乎与Decorating python class methods, how do I pass the instance to the decorator?
相似答案 0 :(得分:28)
像这样使用descriptor protocol:
import functools
class cacher(object):
def __init__(self, f):
self.f = f
self.cache = {}
def __call__(self, *args):
fname = self.f.__name__
if (fname not in self.cache):
self.cache[fname] = self.f(self,*args)
else:
print "using cache"
return self.cache[fname]
def __get__(self, instance, instancetype):
"""Implement the descriptor protocol to make decorating instance
method possible.
"""
# Return a partial function with the first argument is the instance
# of the class decorated.
return functools.partial(self.__call__, instance)
修改:
它是如何工作的?
在装饰器中使用描述符协议将允许我们使用正确的实例作为self访问装饰的方法,也许某些代码可以帮助更好:
现在我们要做的事情:
class Session(p.Session):
...
@cacher
def get_something(self):
print "get_something called with self = %s "% self
return self.pl.get_something()
相当于:
class Session(p.Session):
...
def get_something(self):
print "get_something called with self = %s "% self
return self.pl.get_something()
get_something = cacher(get_something)
所以现在get_something是一个cacher的实例。因此,当我们调用方法get_something时,它将被转换为此(由于描述符协议):
session = Session()
session.get_something
# <==>
session.get_something.__get__(get_something, session, <type ..>)
# N.B: get_something is an instance of cacher class.
因为:
session.get_something.__get__(get_something, session, <type ..>)
# return
get_something.__call__(session, ...) # the partial function.
所以
session.get_something(*args)
# <==>
get_something.__call__(session, *args)
希望这能解释它是如何工作的:)
答案 1 :(得分:3)
闭包通常是更好的方法,因为您不必使用描述符协议。保存调用跨越调用的可变状态比使用类更容易,因为您只是将可变对象粘贴在包含范围内(对不可变对象的引用可以通过nonlocal
关键字处理,也可以通过将它们存储在可变对象中来处理比如单项目表。)
#this is the decorator
from functools import wraps
def cacher(f):
# No point using a dict, since we only ever cache one value
# If you meant to create cache entries for different arguments
# check the memoise decorator linked in other answers
print("cacher called")
cache = []
@wraps(f)
def wrapped(*args, **kwds):
print ("wrapped called")
if not cache:
print("calculating and caching result")
cache.append(f(*args, **kwds))
return cache[0]
return wrapped
class C:
@cacher
def get_something(self):
print "get_something called with self = %s "% self
C().get_something()
C().get_something()
如果您不完全熟悉闭包的工作方式,那么添加更多的印刷语句(如上所述)可以说明问题。您将看到cacher
仅在函数定义时被调用,但每次调用该方法时都会调用wrapped
。
这确实突出了你需要注意备忘技术和实例方法的方法 - 如果你不小心考虑self
值的变化,你最终会在实例之间共享缓存的答案,这可能不是你想要的。
答案 2 :(得分:1)
首先,您明确地将cacher
对象作为第一个参数传递到以下行:
self.cache[fname] = self.f(self,*args)
Python会自动将self
添加到方法的参数列表中。它将类命名空间中定义的函数(但不是其cacher
对象的其他可调用对象!)转换为方法。为了得到这样的行为,我看到两种方式:
self
参数,因为它是在memoize decorator recipe中完成的。