【问题标题】:Decorate method with decorator class用装饰器类装饰方法
【发布时间】:2020-01-11 09:12:20
【问题描述】:

我正在尝试为方法创建一个包装器。 装饰器本身被实现为一个覆盖__call__ 方法的类。

这适用于装饰函数,但如果以这种方式装饰方法,self 不会绑定到装饰函数。

以下是一个最小示例:

import functools


class Wrapper:

    def __init__(self, func) -> None:
        super().__init__()
        self.__func = func
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        print("PreBar")
        result = self.__func(*args, **kwargs)
        print("PostBar")
        return result


def MyDecorator(func):
    return Wrapper(func)


class Foo:
    baz = "Baz"

    @MyDecorator
    def bar(self):
        print(self.baz)
        return self.baz


if __name__ == '__main__':
    foo = Foo()
    print(foo.bar)
    print(f"Result of foo.bar(): {foo.bar()}")

    print(Foo.bar)
    print(f"Result of Foo.bar(foo): {Foo.bar(foo)}")

这会产生以下错误,这意味着 self 不会像普通方法一样自动传递给 __call__ 调用:

Traceback (most recent call last):
  File "/mypath/test.py", line 35, in <module>
    print(f"Result of foo.bar(): {foo.bar()}")
  File "/mypath/test.py", line 14, in __call__
    result = self.__func(*args, **kwargs)
TypeError: bar() missing 1 required positional argument: 'self'

问题:返回的可调用包装对象如何绑定到self? 将对象包装在函数中以让 python 进行绑定是一种选择,但在我的情况下不是这样,因为需要实际的对象。

How can I decorate an instance method with a decorator class? 的问题类似于我想要实现的目标。如果只需要绑定,那里提供的解决方案就可以工作。我需要的是,访问属性时返回的对象实例是Wrapper 类型,而不是functools.partial 类型。

second answer to that question 也不起作用,因为__get__ 返回的实例是method 类型而不是Wrapper

【问题讨论】:

标签: python


【解决方案1】:

以下帖子通过简单地将对象包装在一个函数中来解决该问题,以便 python 可以将self 与本机机制绑定:Decorating method (class methods overloading)

正如问题中提到的,这对我来说不是一个选择。该帖子还提到了替代方案:为Wrapper 类实现描述符协议。问题Decorating a method 也解决了这个问题。 问题在于,那里使用的方法仅适用于该类的一个实例。

我想出了以下解决方案:

import functools


class Wrapper:

    def __init__(self, func, instance=None) -> None:
        super().__init__()
        self.__func = func
        self.__instance = instance
        self.__bindings = {}
        functools.update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        print("PreBar")
        if self.__instance is not None:
            args =(self.__instance, ) + args
        result = self.__func(*args, **kwargs)
        print("PostBar")
        return result

    def __get__(self, instance, owner):
        try:
            return self.__bindings[(instance, owner)]
        except KeyError:
            binding = self.__bindings[(instance, owner)] = Wrapper(self.__func, instance)
            return binding


def MyDecorator(func):
    return Wrapper(func)


class Foo:
    baz = "Baz"

    @MyDecorator
    def bar(self):
        print(self.baz)
        return self.baz


if __name__ == '__main__':
    foo = Foo()
    print(foo.bar)
    print(f"Result of foo.bar(): {foo.bar()}")

    print(Foo.bar)
    print(f"Result of Foo.bar(foo): {Foo.bar(foo)}")

不同之处在于函数描述符的__get__ 方法为每个(实例,所有者)对创建一个新的包装器实例。 这样,包装器总是绑定到一个实例,甚至是类本身。

【讨论】:

    猜你喜欢
    • 2014-01-14
    • 1970-01-01
    • 2019-02-03
    • 2020-01-02
    相关资源
    最近更新 更多