应用于方法的可调用对象装饰器在输入时不会得到自我参数

时间:2014-03-20 21:20:55

标签: python functional-programming parameter-passing decorator python-decorators

import functools


class Decor(object):
    def __init__(self, func):
         self.func = func

    def __call__(self, *args, **kwargs):
        def closure(*args, **kwargs):
            print args, kwargs
            return self.func(*args, **kwargs)
        return closure(*args, **kwargs)


class Victim(object):
    @Decor
    def sum(self, a, b):
        return a+b


v = Victim()
v.sum(1, 2)

结果:

(1, 2) {}
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    v.sum(1, 2)
  File "test.py", line 11, in __call__
    return closure(*args, **kwargs)
  File "test.py", line 10, in closure
    return self.func(*args, **kwargs)
TypeError: sum() takes exactly 3 arguments (2 given)

如何获得该方法的self参数?

更新 我已经设法创建了一个更有用的Martijn答案的改编,它返回Decor对象以响应__get__,但同时绑定self参数,当它被称为对象的方法。使用此版本,您可以说例如在Victim.sum.hooks.append(my_favorite_function)之前将调用my_favorite_functionVictim.sum警告:此版本线程不安全

class Decor(object):
    def __init__(self, func):
        self.func = func
        self.hooks = []
        wraps(self.func)(self)

    def __get__(self, instance, klass):
        if instance != None: self.instance = instance
        if klass != None: self.klass = klass
        return self

    def __call__(self, *args, **kwargs):
        def closure(*args, **kwargs):
           for function in self.hooks:
               function(*args, **kwargs)
           func = self.func
           retval = func(*args, **kwargs) #kwargs_copy #called with notify = False
           return retval
        return closure.__get__(self.instance, self.klass)(*args, **kwargs)

1 个答案:

答案 0 :(得分:7)

Python函数充当descriptors,这意味着无论何时访问类或实例上的函数,都会调用它们的.__get__() method并返回一个方法对象,该对象保留对原始函数的引用,例如,对实例的引用。然后,方法对象充当包装器;在调用时,它们调用底层函数并将实例引用传递为self

另一方面,您的可调用类对象不实现描述符协议,它没有.__get__()方法,因此它永远不会有机会绑定到实例。您必须自己实现此功能:

class Decor(object):
    def __init__(self, func):
         self.func = func

    def __get__(self, instance, owner):
        if instance is None:
            return self
        d = self
        # use a lambda to produce a bound method
        mfactory = lambda self, *args, **kw: d(self, *args, **kw)
        mfactory.__name__ = self.func.__name__
        return mfactory.__get__(instance, owner)

    def __call__(self, instance, *args, **kwargs):
        def closure(*args, **kwargs):
            print instance, args, kwargs
            return self.func(instance, *args, **kwargs)
        return closure(*args, **kwargs)

演示:

>>> class Victim(object):
...     @Decor
...     def sum(self, a, b):
...         return a+b
... 
>>> v = Victim()
>>> v.sum
<bound method Victim.sum of <__main__.Victim object at 0x11013d850>>
>>> v.sum(1, 2)
<__main__.Victim object at 0x11013d850> (1, 2) {}
3

一个好主意是将您绑定的实例直接存储在Decor实例上;这是一个类属性,在实例之间共享。设置self.instance既不是线程安全的,也不允许存储方法以供以后调用;最近的__get__电话会改变self.instance并导致难以解决的错误。

您始终可以返回自定义代理对象而不是方法:

class DecorMethod(object):
    def __init__(self, decor, instance):
        self.decor = decor
        self.instance = instance

    def __call__(self, *args, **kw):
        return self.decor(instance, *args, **kw)

    def __getattr__(self, name):
        return getattr(self.decor, name)

    def __repr__(self):
        return '<bound method {} of {}>'.format(self.decor, type(self))

并在Decor.__get__中使用它而不是生成方法:

def __get__(self, instance, owner):
    if instance is None:
        return self
    return DecorMethod(self, instance)

此处DecorMethod将任何未知属性请求传递回Decor装饰器实例:

>>> class Victim(object):
...     @Decor
...     def sum(self, a, b):
...         return a + b
... 
>>> v = Victim()
>>> v.sum
<bound method <__main__.Decor object at 0x102295390> of <class '__main__.DecorMethod'>>
>>> v.sum.func
<function sum at 0x102291848>