Python:如何装饰特殊的(暗淡的)方法

时间:2019-04-06 14:56:12

标签: python python-decorators

包装一个特殊的方法可以,但是对实例的行为没有理想的效果。

例如,如果我调用a.__call__,装饰a.__call__(x)方法(实例a)的确会生效,而如果我调用a(x)则不会生效。

考虑以下使装饰器预处理输入的函数:

def input_wrap_decorator(preprocess):
    def decorator(func):
        def func_wrapper(*args, **kwargs):
            return func(preprocess(*args, **kwargs))
        return func_wrapper
    return decorator

考虑这个简单的类:

class A:
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

其惊人功能的演示:

>>> a = A()
>>> a(7)
'A(7)'

现在说我想做一些关键的事情:使用__call__input_wrap_decorator的所有输入乘以10。这是发生了什么:

>>> a = A()
>>> a.__call__ = input_wrap_decorator(preprocess=lambda x: x * 10)(a.__call__)
>>> a.__call__(7)  # __call__ works as expected
'A(70)'
>>> a(7)  # but a(.) does not!
'A(7)'

正在发生某种模糊的事情,只有大蟒蛇才知道...

1 个答案:

答案 0 :(得分:3)

Special method lookup中所述,

  

对于自定义类,仅对特殊方法进行隐式调用   如果定义在对象的类型上,而不是在   对象的实例字典

因此,您可以这样做:

def input_wrap_decorator(preprocess):
    def decorator(func):
        def func_wrapper(self, *args, **kwargs):
            return func(self, preprocess(*args, **kwargs))
        return func_wrapper
    return decorator

class A:
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

a = A()

# A.__call__ will be used by a(7), not a.__call__
A.__call__ = input_wrap_decorator(preprocess=lambda x: x * 10)(A.__call__)

print(a.__call__(7))
# A(70)
print(a(7))
# A(70)

请注意,我在self中隔离了func_wrapper,这样它就不会与其他参数一起传递给preprocess

当然,您可以将语法糖用作装饰器:

class A:
    @input_wrap_decorator(preprocess=lambda x: x * 10)
    def __call__(self, k):
        return "{}({})".format(self.__class__.__name__, k)

a = A()    
print(a.__call__(7))
# A(70)
print(a(7))
# A(70)