Python装饰,自我混淆了

时间:2011-03-29 08:45:17

标签: python decorator

我是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?

相似

3 个答案:

答案 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对象的其他可调用对象!)转换为方法。为了得到这样的行为,我看到两种方式:

  1. 使用闭包将装饰器更改为返回功能。
  2. 实现描述符协议以自己传递self参数,因为它是在memoize decorator recipe中完成的。