Python装饰,自我混淆了

Rab*_*ski 17 python python-decorators

我是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()
Run Code Online (Sandbox Code Playgroud)

当我运行这个时,我得到:

get_something called with self = <__main__.cacher object at 0x020870F0> 
Traceback:
...
AttributeError: 'cacher' object has no attribute 'pl'
Run Code Online (Sandbox Code Playgroud)

对于我所在的那条线 self.cache[fname] = self.f(self,*args)

问题 - 显然,问题是selfcacher对象而不是Session实例,它实际上没有pl属性.但是我找不到如何解决这个问题.

解决方案我考虑过但不能使用 - 我想让装饰器类返回一个函数而不是一个值(如本文 2.1节所述),以便self在正确的上下文中进行评估,但这是不可能的因为我的装饰器是作为一个类实现的,并使用了内置 __call__方法.然后我想不要为我的装饰器使用一个类,所以我不需要__call__method,但我不能这样做,因为我需要在装饰器调用之间保持状态(即跟踪self.cache属性中的内容) .

问题 - 所以,除了使用全局cache字典变量(我没有尝试,但假设会起作用),有没有其他方法使这个装饰工作?

编辑:这个SO问题似乎与装饰python类方法类似,如何将实例传递给装饰器?

mou*_*uad 31

像这样使用描述符协议:

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)
Run Code Online (Sandbox Code Playgroud)

编辑:

它是如何工作的?

在装饰器中使用描述符协议将允许我们使用正确的实例作为self访问装饰的方法,也许一些代码可以帮助更好:

现在我们什么时候做:

class Session(p.Session):
    ...

    @cacher
    def get_something(self):
        print "get_something called with self = %s "% self
        return self.pl.get_something()
Run Code Online (Sandbox Code Playgroud)

相当于:

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)
Run Code Online (Sandbox Code Playgroud)

所以现在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.
Run Code Online (Sandbox Code Playgroud)

因为:

session.get_something.__get__(get_something, session, <type ..>)
# return
get_something.__call__(session, ...) # the partial function.
Run Code Online (Sandbox Code Playgroud)

所以

session.get_something(*args)
# <==>
get_something.__call__(session, *args)
Run Code Online (Sandbox Code Playgroud)

希望这将解释它是如何工作的:)