【问题标题】:Class decorator not binding self类装饰器不绑定自我
【发布时间】:2020-06-05 01:51:02
【问题描述】:

我已经定义了一个类,该类将装饰器定义为该类的方法。 装饰器本身会创建第二个类的可调用实例来替换被装饰的方法。 由于装饰方法现在实际上是一个类,我可以在它上面调用方法。 在我的(虚构的,最小的)示例中,我想使用每个方法的自定义最大回调数注册回调。

class CallbackAcceptor:

    def __init__(self, max_num_callbacks, func):
        self._func = func
        self._max_num_callbacks = max_num_callbacks
        self._callbacks = []

    def __call__(self, *args, **kwargs):
        # This ends up being called when the decorated method is called
        for callback in self._callbacks:
            print(f"Calling {callback.__name__}({args}, {kwargs})")
            callback(*args, **kwargs)
        return self._func(*args, **kwargs)  # this line is the problem, self is not bound

    def register_callback(self, func):
        # Here I can register another callback for the decorated function
        if len(self._callbacks) < self._max_num_callbacks:
            self._callbacks.append(func)
        else:
            raise RuntimeError(f"Can not register any more callbacks for {self._func.__name__}")
        return func


class MethodsWithCallbacksRegistry:

    def __init__(self):
        self.registry = {}  # Keep track of everything that accepts callbacks

    def accept_callbacks(self, max_num_callbacks: int = 1):

        def _make_accept_callbacks(func):
            # Convert func to an CallbackAcceptor instance so we can register callbacks on it
            if func.__name__ not in self.registry:
                self.registry[func.__name__] = CallbackAcceptor(max_num_callbacks=max_num_callbacks, func=func)
            return self.registry[func.__name__]

        return _make_accept_callbacks

函数的一切都按预期工作,但是当我装饰类方法时它会中断,因为类实例未绑定到装饰方法:

registry = MethodsWithCallbacksRegistry()

@registry.accept_callbacks(max_num_callbacks=1)
def bar(i):
    return i * 10

@bar.register_callback
def bar_callback(*args, **kwargs):
    print("Bar Callback")

print(bar(i=10))  # Works fine, prints "Bar Callback" and then 100

现在如果我定义一个接受回调的方法:

class Test:

    @registry.accept_callbacks(max_num_callbacks=1)
    def foo(self, i):
        return i * 2

@Test.foo.register_callback
def foo_callback(*args, **kwargs):
    print("Foo Callback")

如果我明确地传递 self ,它会起作用,但如果我只是假设实例已绑定,则不会:

t = Test()
# Note that I pass the instance of t explicitly as self
Test.foo(t, i=5)  # Works, prints "Foo Callback" and then 10
t.foo(t, i=5)  # Works, prints "Foo Callback" and then 10

t.foo(i=5)  # Crashes, because self is not passed to foo

这是回溯:

Traceback (most recent call last):
  File "/home/veith/.PyCharmCE2019.3/config/scratches/scratch_4.py", line 62, in <module>
    t.foo(i=5)
  File "/home/veith/.PyCharmCE2019.3/config/scratches/scratch_4.py", line 13, in __call__
    return self._func(*args, **kwargs)  # this line is the problem, self is not bound
TypeError: foo() missing 1 required positional argument: 'self'

我一直认为t.foo(i=5) 基本上是Test.foo(t, i=5) 通过描述符的语法糖,但似乎我错了。所以这是我的问题:

  1. 这不按预期工作的原因是什么?
  2. 我必须做些什么才能让它发挥作用?

谢谢!

PS:我使用的是 python 3.8

【问题讨论】:

    标签: python callback python-decorators python-descriptors


    【解决方案1】:

    如果您将CallbackAcceptor 设为descriptor,它的工作方式如下:

    class CallbackAcceptor:
    
        def __init__(self, max_num_callbacks, func):
            self._func = func
            self._max_num_callbacks = max_num_callbacks
            self._callbacks = []
    
        def __call__(self, *args, **kwargs):
            # This ends up being called when the decorated method is called
            for callback in self._callbacks:
                print(f"Calling {callback.__name__}({args}, {kwargs})")
                callback(*args, **kwargs)
    
            return self._func(*args, **kwargs)
    
        def register_callback(self, func):
            # Here I can register another callback for the decorated function
            if len(self._callbacks) < self._max_num_callbacks:
                self._callbacks.append(func)
            else:
                raise RuntimeError(f"Can not register any more callbacks for {self._func.__name__}")
            return func
    
        # Implementing __get__ makes this a descriptor
        def __get__(self, obj, objtype=None):
            if obj is not None:
                # the call is made on an instance, we can pass obj as the self of the function that will be called
                return functools.partial(self.__call__, obj)
            # Called on a class or a raw function, just return self so we can register more callbacks
            return self
    

    现在调用按预期工作:

    print(bar(i=10))
    # Bar Callback
    # 100
    
    t = Test()
    t.foo(i=5)
    # Foo Callback
    # 10
    
    t.foo(t, i=5)
    # TypeError: foo() got multiple values for argument 'i'
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-06
      • 2021-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-25
      • 2011-06-30
      • 2021-05-10
      相关资源
      最近更新 更多