使用装饰器在Python3中实现Observer Pattern

时间:2018-01-19 08:23:35

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

这个问题一般不关于观察者模式。它专注于在该模式中使用装饰器。问题基于类似问题的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)。

3 个答案:

答案 0 :(得分:3)

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

请删除Observable这是一个javaism的东西,在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

答案 1 :(得分:1)

即使线程有点旧(可能问题已经解决了),我还是希望与我一起解决“装饰观察者模式”问题:

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

我创建了一个实现装饰器的包,该装饰器将观察者观察到的方法/属性添加到python类。我也设法在Django项目中使用了该软件包,但是做了一些改动(.observers属性未持久存储在数据库中,因此每次我希望通知它们时,都必须将观察者列表加载到其中)。 / p>

这是一个实现示例:

原始代码:

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...

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

答案 2 :(得分:0)

我最近正在寻找类似的东西,这就是我的想法。它可以通过拦截__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()