如何将参数传递给方法装饰器

3
我有一个像这样的方法装饰器。
class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, f):
            self.f = f
            self.msg = msg

        def __get__(self, instance, _):
            def wrapper(test):
                print(self.msg)
                print(instance.start)    
                self.f(instance, test)
                return self.f
            return wrapper

    @Decorator
    def p1(self, sent):
        print(sent)

c = MyClass()
c.p1('test')

这个方法很好用。但是,如果我想要向装饰器传递一个参数,方法就不再作为参数传递,并且会出现以下错误:

TypeError: init()缺少1个位置参数:'f'

class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, f, msg):
            self.f = f
            self.msg = msg

        def __get__(self, instance, _):
            def wrapper(test):
                print(self.msg)
                print(instance.start)    
                self.f(instance, test)
                return self.f
            return wrapper

    @Decorator(msg='p1')
    def p1(self, sent):
        print(sent)

    @Decorator(msg='p2')
    def p2(self, sent):
        print(sent)

如何向装饰器类传递参数,为什么它会覆盖该方法?
3个回答

2

装饰器将会被调用

在您的情况下,您将在__call__方法中接收函数作为参数。

class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, msg):
            self.msg = msg

        def __call__(self, f):
            self.f = f
            return self

        def __get__(self, instance, _):
            def wrapper(test):
                print(self.msg)
                self.f(instance, test)
                return self.f
            return wrapper

    @Decorator(msg='p1')
    def p1(self, sent):
        print(sent)

    @Decorator(msg='p2')
    def p2(self, sent):
        print(sent)

你的第一个例子能够工作,因为调用Class会创建一个实例,并且函数是参数。

但是在你的第二个例子中,你手动调用Class来设置msg参数,所以装饰过程调用剩下的部分,即:实例并且进入__call__方法。


我执行你的代码时出现了这个错误信息:TypeError: 'NoneType' object is not callable。 - Nijan
__call__ 方法需要返回 self,否则装饰器语法会将方法替换为 None(显然这是没有用的)。 - Blckknght

2

在这里,描述符协议并没有什么作用。您可以简单地将函数本身传递给__call__,并返回包装函数,而不会失去对实例的访问:

class MyClass:
    def __init__(self):
        self.start = 0

    class Decorator:
        def __init__(self, msg):
            self.msg = msg

        def __call__(self, f):
            def wrapper(instance, *args, **kwargs):
                print(self.msg)
                # access any other instance attributes
                return f(instance, *args, **kwargs)
            return wrapper

    @Decorator(msg='p1')
    def p1(self, sent):
        print(sent)

>>> c = MyClass()
>>> c.p1('test')
p1
test

我需要描述符,因为我需要访问MyClass实例的属性(不在我的代码中)。我已经添加了一行来澄清。 - Nijan
我让我的代码更加清晰易懂。因为实例对象总是作为函数参数传递,所以你也可以在这里访问它。 - user2390182
是的,这是目前为止最好的解决方案。 - Nijan

1
当您使用参数调用装饰器时,您调用的函数实际上并不像装饰器本身那样工作。相反,它是一个装饰器工厂(一个函数或其他可调用对象,返回将充当装饰器的内容)。通常,您通过添加额外的嵌套函数来解决这个问题。由于您正在使用类定义装饰器,因此直接这样做有点棘手(尽管您可能可以使其正常工作)。但是,只要在包装器函数中处理self(它现在将是MyClass的实例,而不是Decorator类的实例),您似乎并不需要您的装饰器是一个类:
class MyClass:
    def __init__(self):
        self.start = 0

    def decorator_factory(msg):
        def decorator(f):
            def wrapper(self, test): # you might want to replace test with *args and **kwargs
                print(msg)
                print(self.start)
                return f(self, test)
            return wrapper
        return decorator

    @decorator_factory(msg='p1')
    def p1(self, sent):
        print(sent)

    @decorator_factory(msg='p2')
    def p2(self, sent):
        print(sent)

我将装饰器工厂的名称命名为这样是为了明确嵌套函数的不同级别,但你当然应该使用实际有意义的顶层名称来适应你的用例。你可能还想将其移出类命名空间,因为它将可供所有 MyClass 实例调用(可能会产生愚蠢的结果,因为它不是一种方法)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接