使用装饰器在Python3中实现观察者模式

问题描述:

这个问题通常与观察者模式无关.它着重于该模式下装饰器的使用.该问题基于类似问题的答案.

This question is not in general about the observer pattern. It is focused on the use of decorators in that pattern. The question is based on the answer of a similar question.

#!/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'

由于此错误,此代码无法正常工作.

This code doesn't work because of this error.

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

It is unclear to me how to use a decorator for to register the observers (e.g. TheGUIElement).

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

To register the callback, you need to have an actual object. In your code, how is @Observable.register_observer supposed to find which instance is should register on?

请删除Observable这是一种Java风格的东西,在python中很麻烦.

Please drop that Observable thing that's a javaism, cumbersome in 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)

我有意删除了自动通知,以说明注册本身.

I intentionally removed automatic notifications to illustrate registration by itself.

让我们重新添加.但是使用该Observable类没有意义.只需定义一个事件类,我们就可以减轻它的负担.

Let's add it back. But there is no point using that Observable class. Let's make it lighter, simply defining an event class.

#!/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

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

As you probably noted now, the decorator syntax is useful in those circumstances:

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

现在,如果您有许多为什么不将它们排除在外,那么您拥有的那些手动获取器/设置器也很麻烦?

Now, those manual getters/setters you have are cumbersome as well, if you have many why not factor them out?

#!/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

作为参考,这三个程序都输出完全相同的东西:

For reference, all three programs output the exact same thing:

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