首页 > 解决方案 > Python:如何装饰一个特殊的(dunder)方法

问题描述

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

例如,如果我调用,装饰a.__call__方法(实例 a)确实会生效a.__call__(x),但如果我调用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__10,使用input_wrap_decorator. 这是发生的事情:

>>> 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)'

一些只有蟒蛇大人才能知道的晦涩难懂的事情正在发生……

标签: pythonpython-decorators

解决方案


特殊方法查找中所述,

对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中

所以,你可以这样做:

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)

请注意,我隔离selffunc_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)

推荐阅读