将装饰器类应用于类方法

时间:2016-04-04 13:03:27

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

考虑以下装饰者:

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

正如你所看到的,我需要向装饰器传递一些额外的参数,在这种情况下 - 我需要连接到的信号。 如何通过自我?

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

  

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

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

update.emit()

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

1 个答案:

答案 0 :(得分:2)

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实例保持活动,因为信号仍然引用该实例的绑定方法),或者您的信号处理程序过早清理,因为绑定的方法存储在弱引用中,因此不会有任何其他引用以保持它们存活。

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