【问题标题】:Bind callback to the object instance将回调绑定到对象实例
【发布时间】:2016-03-11 07:40:18
【问题描述】:

我想将一个类方法绑定到对象实例,这样当方法作为回调调用时,它仍然可以访问对象实例。我正在使用事件发射器来生成和触发事件。

这是我的代码:

#!/usr/bin/env python3
from pyee import EventEmitter

class Component(object):
  _emiter = EventEmitter()

  def emit(self, event_type, event):
    Component._emiter.emit(event_type, event)

def listen_on(event):
  def listen_on_decorator(func):
    print("set event")
    Component._emiter.on(event, func)
    def method_wrapper(*args, **kwargs):
      return func(*args, **kwargs)
    return method_wrapper
  return listen_on_decorator

class TestComponent(Component):

  @listen_on('test')
  def on_test(self, event):
    print("self is " + str(self))
    print("FF" + str(event))

if __name__ == '__main__':
  t = TestComponent()
  t.emit('test', { 'a': 'dfdsf' })

如果您运行此代码,则会引发错误:

  File "component.py", line 29, in <module>                                                                                                                                                         [0/1889]
    t.emit('test', { 'a': 'dfdsf' })
  File "component.py", line 8, in emit
    Component._emiter.emit('test', event)
  File "/Users/giuseppe/.virtualenvs/Forex/lib/python3.4/site-packages/pyee/__init__.py", line 117, in emit
    f(*args, **kwargs)
  File "component.py", line 14, in method_wrapper
    return func(*args, **kwargs)
TypeError: on_test() missing 1 required positional argument: 'event'

这是由于调用方法on_test时缺少self引起的。

【问题讨论】:

    标签: python python-3.x events callback


    【解决方案1】:

    基于 OP 在 cmets 上对另一个答案提出的额外要求,有这种替代方法。

    这里,装饰器仅用于标记哪些方法将作为发射器工作 - 发射器的实际注册仅在实际实例化类时完成,基于标记的方法。

    #!/usr/bin/env python3
    from pyee import EventEmitter
    
    class Component(object):
        _emiter = EventEmitter()
        def __init__(self):
            for attr_name in dir(self):
                method = getattr(self, attr_name)
                if hasattr(method, "_component_emitter_on"):
                    for event in method._component_emitter_on:
                        self._emiter.on(event, method)
                self.attr_name = method
    
        def emit(self, event_type, event):
            Component._emiter.emit(event_type, event)
    
    
    def listen_on(event):
        def listen_on_decorator(func):
            print("set event")
            func._component_emitter_on = getattr(func, "_component_emitter_on", []) + [event]
            return func
        return listen_on_decorator
    
    
    class TestComponent(Component):
    
        @listen_on('test')
        def on_test(self, event):
            print("self is " + str(self))
            print("FF" + str(event))
    
    if __name__ == '__main__':
        t = TestComponent()
        t.emit('test', { 'a': 'dfdsf' })
    

    (请注意,我还删除了装饰器上的冗余间接级别 - 如果装饰器不会替换可调用对象本身,只需对其(或使用它)进行注释,它们就不需要创建另一个可调用对象)

    【讨论】:

    • 太棒了!这就是我需要的,你试过运行代码吗?您确定不需要额外的间接级别吗?因为我收到以下错误 `TypeError: on_test() 正好需要 2 个参数(给定 1 个)`
    • 我没有运行,因为我没有 pyee - 但我发现上面有一个错误 - 我会尽快修复它。
    • 现在已修复 - 很难出错,因为它们实际上是两个,一个隐藏另一个 - 我有一个错字,在我的注释属性上省略了“_” - 并且忘记删除在装饰器中注册事件的调用。 (仅在实例化时完成)
    • 我明白了,我已经用类似的方式修复了..我唯一不明白的是这一行`self.attr_name = method`为什么需要将attr_name设置为原始值?
    • 每当从 Python 的实例中检索方法时,Python 都会创建一个 new 方法实例——它指向类主体中声明的函数。当我在实例中执行self.attr_name = method 时,我会覆盖该实例的此检索机制——该方法现在是该实例的一个属性。 (您可以检查实例的__dict__ 来检查它)。因此,这可以确保看到的 instance.on_test 是实际装饰为事件接收者的那个。
    【解决方案2】:

    实例绑定方法不存在,因为很难想象否则,在解析类主体时 - 即应用装饰器时。

    这意味着您的 on_test 方法的行为就像一个函数,此时没有“意识到”它的类或实例。当从对象实例中检索方法时,Python 确实会创建一个特殊的可调用对象(具有“方法”类型),它本质上会在对原始函数的调用中插入 self 参数。

    使其工作的一种方法是装饰这个可调用对象(绑定方法本身)而不是原始函数。当然,它只存在于类被实例化的时候。

    幸运的是,在 Python 中,decorators are mostly a syntactic shortcut to a function call passing the decorated function as a parameter - 所以你可以或多或少地重写你的 TestComponent 类:

    class TestComponent(Component):
    
      def __init__(self, *args, **kw):
          super().__init__(*args, **kw)
          self.on_test = listen_on('test')(self.on_test)
    
      def on_test(self, event):
        print("self is " + str(self))
        print("FF" + str(event))
    

    请注意,当我们在修饰后重新分配 self.on_test 实例成员时,它在通过实例调用时不再表现为方法 - 这将只是一个函数调用,没有 self 的魔法插入 - 但是,在self.on_test 被检索并作为参数传递给该行右侧的装饰器时,self 参数已经绑定到该可调用对象。

    ** 替代 ** cmets 建议上述使用看起来不像使用了装饰器 - 可以重写它以实际使用装饰器语法,只需这样做 - 尽管这将隐藏静态代码检查器的方法,例如 IDE 自动完成引擎和 linter:

    class TestComponent(Component):
    
      def __init__(self, *args, **kw):
          super().__init__(*args, **kw)
    
          @listen_on('test')
          def on_test(self, event):
              print("self is " + str(self))
              print("FF" + str(event))
    
          self.on_test = on_test  
    

    【讨论】:

    • 您好,谢谢您的回答。但是这个解决方案不使用装饰器。我不想这样做:self.on_test = listen_on('test')(self.on_test) 在构造函数中。
    • 是的——我确实使用了装饰器——这个调用正是装饰器在内部所做的,
    • 是的,对不起,我没有正确表达自己。我想保留@listen_on 语法。我不喜欢在构造函数中添加一行来注册侦听器方法。它不是自描述的。如果我选择那个解决方案,我根本不需要装饰器。相反,我想做的是从继承的类中隐藏发射器逻辑。
    • 在构造函数上注册侦听器方法是您必须忍受的一件事 - 是否使用装饰器 - 实例仅在创建后才存在。但是,您可以拥有一个基类,它将在子类的所有修饰方法中为每个实例注册发射器逻辑 - 这仍然可以在 __init__ 上完成,要复杂一个数量级,但仅限于基类 __init__
    猜你喜欢
    • 2012-06-17
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-29
    相关资源
    最近更新 更多