简体   繁体   English

用装饰器类装饰方法

[英]Decorate method with decorator class

I'm trying to create a wrapper for methods. 我正在尝试为方法创建包装。 The decorator itself is implemented as a class that overwrites the __call__ method. 装饰器本身被实现为覆盖__call__方法的类。

This works for decorating functions, but if a method is decorated this way, self is not bound to the decorated function. 这适用于修饰函数,但是如果以这种方式修饰方法,则self不会绑定到修饰的函数。

The following is a minimal example: 以下是一个最小的示例:

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)}")

This produces the following error, meaning that self is not automatically passed to the __call__ invocation as with normal methods: 这将产生以下错误,这意味着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'

Question: How can the returned callable wrapper object be bound to self ? 问题:如何将返回的可调用包装器对象绑定到self Wrapping the object in a function to let python do the binding is an option, but not in my case, since the actual object is needed. 将对象包装在函数中以让python进行绑定是一种选择,但是在我的情况下不是这样,因为需要实际的对象。

The question How can I decorate an instance method with a decorator class? 问题如何用装饰器类装饰实例方法? is similar to what i want to achieve. 类似于我要实现的目标。 The solution provided there works if only the binding is required. 如果仅需要绑定,则此处提供的解决方案有效。 What i require though, is that the object instance that is returned when accessing the attribute is of type Wrapper , not of type functools.partial . 我需要的是,访问属性时返回的对象实例是Wrapper类型,而不是functools.partial类型。

The second answer to that question also does not work, since the instance returned by __get__ is of type method and not Wrapper . 第二个问题的答案也不起作用,因为通过返回的实例__get__是类型的method ,而不是Wrapper

The following post addresses the Problem by simply wrapping the object in a function such that python can bind self with the native mechanism: Decorating method (class methods overloading) 以下文章通过简单地将对象包装在函数中来解决该问题,以便python可以将self与本机机制绑定: 装饰方法(类方法重载)

As mentioned in the question this is not an option for me. 正如问题中提到的那样,这不是我的选择。 The post also mentions the alternative: Implement the descriptor protocol for the Wrapper class. 该帖子还提到了替代方法:为Wrapper类实现描述符协议。 The question Decorating a method addresses this as well. 装饰方法的问题也解决了这个问题。 The problem there is, that the approach used there only works with one instance of the class. 那里的问题是,那里使用的方法仅适用于该类的一个实例。

I came up with the following solution: 我想出了以下解决方案:

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)}")

The difference being that the __get__ method for function descriptors creates a new Wrapper instance for each (instance, owner) pair. 区别在于,函数描述符的__get__方法为每个(实例,所有者)对创建一个新的Wrapper实例。 This way the wrapper is always bound to exactly one instance, or even the class itself. 通过这种方式,包装程序始终始终仅绑定到一个实例,甚至是类本身。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM