【问题标题】:Using decorators to implement Observer Pattern in Python3Python3中使用装饰器实现观察者模式
【发布时间】:2018-06-28 10:36:30
【问题描述】:

这个问题一般不是关于观察者模式的。它专注于在该模式中使用装饰器。该问题基于类似问题的answer

#!/usr/bin/env python3

class Observable:
    """
        The object that need to be observed. Alternative names are 'Subject'.
        In the most cases it is a data object.
    """
    def __init__(self):
        self._observers = []

    def register_observer(self, callback):
        self._observers.append(callback)
        return callback

    def _broadcast_observers(self, *args, **kwargs):
        for callback in self._observers:
            callback(*args, **kwargs)


class TheData(Observable):
    """
        Example of a data class just for demonstration.
    """
    def __init__(self, data):
        Observable.__init__(self)
        self._data = data

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, data):
        self._data = data
        self._broadcast_observers()


class TheGUIElement:
    """
        Example of a gui class (Widget) just for demonstration.
        e. g. it could be a text field in GUI.
    """
    def __init__(self, data):
        self._data = data
        #data.register_observer(self._data_updated)
        self._redraw()

    def _redraw(self):
        print('in _redraw(): ' + data.data)

    @Observable.register_observer
    def _data_updated(self, **kwargs):
        """
            This is the callback that is called by the Observable if the
            data changed.
        """
        print('in _data_updated() - kwargs: {}'.format(kwargs))
        self._redraw()


if __name__ == '__main__':
    data = TheData('DATA')
    gui = TheGUIElement(data)

    data.data = 'SECOND DATA'

由于此错误,此代码不起作用。

Traceback (most recent call last):
  File "./o.py", line 42, in <module>
    class TheGUIElement:
  File "./o.py", line 55, in TheGUIElement
    @Observable.register_observer
TypeError: register_observer() missing 1 required positional argument: 'callback'

我不清楚如何使用装饰器来注册观察者(例如TheGUIElement)。

【问题讨论】:

  • 好吧,就像你的错误消息说:“register_observer() 缺少 1 个必需的位置参数'”:当你的装饰器被调用时,它只接收 self._data_updated 作为参数,而不是 self.data。
  • 这是因为@Observable.register_observerObservable 类而不是实例上调用register_observer

标签: python python-3.x observer-pattern python-decorators


【解决方案1】:

要注册回调,您需要有一个实际对象。在您的代码中,@Observable.register_observer 应该如何找到应该注册的实例?

请放弃 Observable 的 java 主义,在 python 中很麻烦。

看看这个。

#!/usr/bin/env python

class SomeData(object):
    def __init__(self, value):
        self.callbacks = []
        self.foo = value

    def register(self, callback):
        self.callbacks.append(callback)
        return callback

    def notify(self, *args, **kwargs):
        for callback in self.callbacks:
            callback(self, *args, **kwargs)

class SomeGUI(object):
    def redraw(self, obj, key, newvalue):
        print('redrawing %s with value %s' % (self, newvalue))


if __name__ == '__main__':
    my_data = SomeData(42)

    # Register some function using decorator syntax
    @my_data.register
    def print_it(obj, key, value):
        print('Key %s changed to %s' % (key, value))

    # Register the SomeGUI element
    my_gui = SomeGUI()
    my_data.register(my_gui.redraw)

    # Try changing it. Note my_data is dumb for now, notify manually.
    my_data.foo = 10
    my_data.notify("foo", 10)

我故意删除了自动通知来说明注册本身。

让我们重新添加它。但是使用那个 Observable 类是没有意义的。让我们更轻松,只需定义一个事件类。

#!/usr/bin/env python3


class Event(object):
    def __init__(self):
        self.callbacks = []

    def notify(self, *args, **kwargs):
        for callback in self.callbacks:
            callback(*args, **kwargs)

    def register(self, callback):
        self.callbacks.append(callback)
        return callback

class SomeData(object):
    def __init__(self, foo):
        self.changed = Event()
        self._foo = foo

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, value):
        self._foo = value
        self.changed.notify(self, 'foo', value)

class SomeGUI(object):
    def redraw(self, obj, key, newvalue):
        print('redrawing %s with value %s' % (self, newvalue))


if __name__ == '__main__':
    my_data = SomeData(42)

    # Register some function using decorator syntax
    @my_data.changed.register
    def print_it(obj, key, value):
        print('Key %s changed to %s' % (key, value))

    # Register the SomeGUI element
    my_gui = SomeGUI()
    my_data.changed.register(my_gui.redraw)

    # Try changing it.
    my_data.foo = 10

您现在可能已经注意到,装饰器语法在这些情况下很有用:

  • 您只有一个注册表。单例或类本身的类都是一阶对象,并且大多数是单例。
  • 您可以动态定义函数并随时注册。

现在,您拥有的那些手动 getter/setter 也很麻烦,如果您有很多,为什么不将它们排除在外?

#!/usr/bin/env python3


class Event(object):
    def __init__(self):
        self.callbacks = []

    def notify(self, *args, **kwargs):
        for callback in self.callbacks:
            callback(*args, **kwargs)

    def register(self, callback):
        self.callbacks.append(callback)
        return callback

    @classmethod
    def watched_property(cls, event_name, key):
        actual_key = '_%s' % key

        def getter(obj):
            return getattr(obj, actual_key)

        def setter(obj, value):
            event = getattr(obj, event_name)
            setattr(obj, actual_key, value)
            event.notify(obj, key, value)

        return property(fget=getter, fset=setter)


class SomeData(object):
    foo = Event.watched_property('changed', 'foo')

    def __init__(self, foo):
        self.changed = Event()
        self.foo = foo



class SomeGUI(object):
    def redraw(self, obj, key, newvalue):
        print('redrawing %s with value %s' % (self, newvalue))


if __name__ == '__main__':
    my_data = SomeData(42)

    # Register some function using decorator syntax
    @my_data.changed.register
    def print_it(obj, key, value):
        print('Key %s changed to %s' % (key, value))

    # Register the SomeGUI element
    my_gui = SomeGUI()
    my_data.changed.register(my_gui.redraw)

    # Try changing it.
    my_data.foo = 10

作为参考,所有三个程序都输出完全相同的内容:

$ python3 test.py
Key foo changed to 10
redrawing <__main__.SomeGUI object at 0x7f9a90d55fd0> with value 10

【讨论】:

  • 感谢您投入这么多时间来回答这个问题。但是您的代码不适合我的 MWE,它是(针对我的主题和标签)Python2。但是,这两点当然不是我不明白你的答案的主要原因。但是,如果您将我的 MWE 用作您的答案的代码基础,它将帮助我(和其他读者)更好地理解您的答案。
  • 我的理解是你完全重组了我的 MWE。我了解您对Event 的使用。但是为什么print_it 是一个全局函数呢?这让 IMO 毫无意义。你的__main__ 看起来比我的 MWE 复杂得多。那么您的方法的优势在哪里?
  • @buhtz 这是python3。您甚至可以在底部看到我使用 python3 test.py 运行代码。
  • @buhtz 因为这就是在 python3 中创建类的方式。不是从object 派生的会创建遗留类,这些类是无用的,但用于迁移python 2.1(和更旧的)代码。事实上在python3中,即使你省略它,它也会默默地为你添加它,并且无论如何都从object派生出来。
  • @spectras:真是太棒了!如果您能详细说明一下(也许通过您博客上的一篇文章......),我将不胜感激。更改 foo 的值如何触发事件,注册函数时会发生什么。我想这对经验丰富的 Python 程序员来说是显而易见的,但它会帮助很多没有这种经验的人。无论如何,伟大的工作!非常感谢
【解决方案2】:

即使线程有点老(可能问题已经解决了),我想分享我对“装饰观察者模式”问题的解决方案:

https://pypi.org/project/notifyr/

我创建了一个包,它实现了将观察者观察到的方法/属性添加到 python 类的装饰器。我也设法在 Django 项目中使用了这个包,但是做了一些修改(.observers 属性没有保存在数据库中,所以每次我希望通知他们时,我都必须将观察者列表加载到其中)。

这是一个实现示例:

原代码:

class Dog(object):
  def __init__(self, name):
      self.name = name

  def bark(self):
      print('Woof')

  def sleep(self):
      print(self.name, 'is now asleep: ZZzzzzZzzZ...')

class Person(object):
  def __init__(self, name):
      self.name = name

  def educate_dog(self, dog):
      print(self.name + ':','Sleep,', dog.name)
      dog.sleep()

假设我们希望一个人在每次动物吠叫时教育一只狗:

from notifyr.agents import observed, observer
from notifyr.functions import target

@observed
class Dog(object):
  def __init__(self, name):
      self.name = name

  @target
  def bark(self):
      print('Woof')

  def sleep(self):
      print(self.name, 'is now asleep: ZZzzzzZzzZ...')

@observer('educate_dog')
class Person(object):
  def __init__(self, name):
      self.name = name

  def educate_dog(self, dog):
      print(self.name + ':','Sleep,', dog.name)
      dog.sleep()

给定装饰类,可以实现以下结果:

d = Dog('Tobby')
p = Person('Victor')

d.attach(p) # Victor is now observing Tobby

d.bark()
# Woof
# Victor: Sleep, Tobby
# Tobby is now asleep: ZZzzzzZzzZ...

这个包仍然非常原始,但它为这种情况提供了一个可行的解决方案。

【讨论】:

    【解决方案3】:

    我最近在寻找类似的东西,这就是我想出的。它通过拦截 __setattr__ 方法来工作——这是一个有用的特技,我打算放在口袋里以备后用。

    def watchableClass(cls):
        """
        Class Decorator!
    
        * If the class has a "dirty" member variable, then it will be
        automatically set whenever any class value changes
        * If the class has an "onChanged()" method, it will be called
        automatically whenever any class value changes
        * All this only takes place if the value is different from what it was
        that is, if myObject.x is already 10 and you set myObject.x=10 
        nothing happens
        * DOES NOT work with getter/setter functions.  But then, you are
        already in a function, so do what you want!
    
        EXAMPLE:
            @watchableClass
            class MyClass:
                def __init__(self):
                    self.dirty=False
                def onChanged(self):
                    print('class has changed')
        """
        if hasattr(cls,'__setattr__'):
            cls.__setattr_unwatched__=cls.__setattr__
            cls.__setattr__=_setObjValueWatchedCascade
        else:
            cls.__setattr__=_setObjValueWatched
        return cls
    
    def _setObjValueWatched(ob,k,v):
        """
        called when an object value is set
        """
        different=not k in ob.__dict__ or ob.__dict__[k]!=v
        if different:
            ob.__dict__[k]=v
            if k not in ('dirty'):
                if hasattr(ob,'dirty'):
                    ob.dirty=True
                if hasattr(ob,'onChanged'):
                    ob.onChanged()
    
    def _setObjValueWatchedCascade(ob,k,v):
        """
        called when an object value is set
        IF the class had its own __setattr__ member defined!
        """
        different=not k in ob.__dict__ or ob.__dict__[k]!=v
        ob.__setattr_unwatched__(k,v)
        if different:
            if k not in ('dirty'):
                if hasattr(ob,'dirty'):
                    ob.dirty=True
                if hasattr(ob,'onChanged'):
                    ob.onChanged()
    

    【讨论】:

    • 如果在类定义之前添加@watchableClass,那么当设置任何类成员时,它都会将该类标记为“脏”(已更改)并调用其 onChanged() 方法(如果有)一。
    猜你喜欢
    • 2011-12-21
    • 1970-01-01
    • 1970-01-01
    • 2017-01-23
    • 1970-01-01
    • 2014-08-16
    • 2013-10-28
    • 1970-01-01
    • 2021-07-10
    相关资源
    最近更新 更多