【问题标题】:apply decorator class to class method将装饰器类应用于类方法
【发布时间】:2016-07-24 01:03:47
【问题描述】:

考虑以下装饰器:

class connector(object):
    def __init__(self, signal):
        self.signal = signal
    def __call__(self, slot_func):
        def wrapper(*args, **kwargs):
            slot_func(*args, **kwargs)
        self.signal.connect(wrapper)

下面的信号,以及我需要装饰的方法类:

from signalslot import Signal

update = Signal()

class manager(object):
    # SOME CODE CUT
    @connector(update)
    def update(self):
        print("I'm updating, yay!!!!")

如您所见,我需要向装饰器传递一些额外的参数,在这种情况下 - 我需要连接的信号。 如何也传递self?

我问这个的原因是,如果我尝试将此装饰器应用于方法而不是函数,它会失败并出现以下错误:

TypeError: update() 缺少 1 个必需的位置参数:'self'

更具体地说,如果我尝试发出信号:

update.emit()

是的,我在那个项目中使用"signalslot"

【问题讨论】:

    标签: python python-3.x decorator signals-slots python-decorators


    【解决方案1】:

    self 必须是 位置 参数,而不是关键字参数:

    def wrapper(self, *args, **kwargs):
        #       ^^^^ You can't use "self=None" here.
        slot_func(self, *args, **kwargs)
    

    如果您需要区分函数和方法,请改为实现descriptor

    但是,如果您尝试连接信号,则需要对每个实例上的绑定方法这样做。您最好在实例创建时连接您的信号

    class manager(object):
        def __init__(self):
            update.connect(self.update)
    
        def update(self):
            print("I'm updating, yay!!!!")
    

    manager.__init__被调用时,你有一个新的实例,然后你可以创建一个self.update绑定方法来接收信号。

    您仍然可以为此使用装饰器,但您最多可以在类级别注册哪些函数可以充当信号处理程序;您必须在实例创建时枚举类上的所有函数,然后绑定所有这些信号:

    class connector(object):
        def __init__(self, signal):
            self.signal = signal
        def __call__(self, slot_func):
            slot_func._signal_handler = self.signal
            return slot_func
    

    还有一个单独的类装饰器来包装class.__init__ 方法:

    from inspect import getmembers, isfunction
    
    def connectsignals(cls):
        signal_handlers = getmembers(
            cls, lambda m: isfunction(m) and hasattr(m, '_signal_handler'))
        init = getattr(cls, '__init__', lambda self: None)
        def wrapper(self, *args, **kwargs):
            init(self, *args, **kwargs)
            for name, handler in signal_handlers:
                handler._signal_handler.connect(handler.__get__(self))
        cls.__init__ = wrapper
        return cls
    

    装饰类以及信号处理程序:

    @connectsignals
    class manager(object):
        @connector(update)
        def update(self):
            print("I'm updating, yay!!!!")
    

    装饰器会在每次创建新实例时连接所有处理程序:

    >>> class Signal(object):
    ...     def connect(self, handler):
    ...         print('connecting {!r}'.format(handler))
    ...
    >>> update = Signal()
    >>> @connectsignals
    ... class manager(object):
    ...     @connector(update)
    ...     def update(self):
    ...         print("I'm updating, yay!!!!")
    ...
    >>> manager()
    connecting <bound method manager.update of <__main__.manager object at 0x105439ac8>>
    <__main__.manager object at 0x105439ac8>
    

    您可能想检查signalslot 项目是否使用弱引用来跟踪信号处理程序,因为您可能会遇到对您创建的任何实例的循环引用问题(其中manager 实例保持活动状态,因为信号仍在引用该实例的绑定方法),或者您的信号处理程序被清理得太早,因为您的绑定方法存储在弱引用中,因此不会有任何 other 引用让他们活着。

    查看signalslot source code,我看到该项目的当前迭代使用硬引用,因此除非您明确这样做,否则您的manager 实例永远不会被清除。仅出于这个原因,我会避免使用方法作为信号处理程序。如果您想改用弱引用,请查看using python WeakSet to enable a callback functionality

    【讨论】:

      猜你喜欢
      • 2020-11-30
      • 2021-04-20
      • 2014-01-14
      • 1970-01-01
      • 2020-05-14
      • 2014-03-20
      • 1970-01-01
      • 2011-10-05
      • 2020-01-11
      相关资源
      最近更新 更多