Python中实现观察者模式的替代方法

6
我在阅读一篇关于如何在Python中实现观察者模式的帖子(链接)时,发现了以下评论:

1)在Python中,你也可以使用普通函数,不需要使用“Observer”类。

2)这是Java程序员转向Python后正在尝试做的事情的绝佳例子 - 他们感觉Python缺少所有那些垃圾,并试图“移植”它。

这些评论暗示观察者模式在Python中并不真正有用,存在其他方法可以达到相同的效果。这是真的吗?如果是,如何实现?
下面是观察者模式的代码:
class Observable(object):

    def __init__(self):
        self.observers = []

    def register(self, observer):
        if not observer in self.observers:
            self.observers.append(observer)

    def unregister(self, observer):
        if observer in self.observers:
            self.observers.remove(observer)

    def unregister_all(self):
        if self.observers:
            del self.observers[:]

    def update_observers(self, *args, **kwargs):
        for observer in self.observers:
            observer.update(*args, **kwargs)

from abc import ABCMeta, abstractmethod

class Observer(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def update(self, *args, **kwargs):
        pass

class AmericanStockMarket(Observer):
    def update(self, *args, **kwargs):
        print("American stock market received: {0}\n{1}".format(args, kwargs))

class EuropeanStockMarket(Observer):
    def update(self, *args, **kwargs):
        print("European stock market received: {0}\n{1}".format(args, kwargs))


if __name__ == "__main__":
    observable = Observable()

    american_observer = AmericanStockMarket()
    observable.register(american_observer)

    european_observer = EuropeanStockMarket()
    observable.register(european_observer)

    observable.update_observers('Market Rally', something='Hello World')

3
你试图解决什么问题?模式只是工具,试图“实现”它们在没有特定上下文应用的情况下是没有意义的。 - spectras
2
该模式本身很有用,但其实现方式类似Java,你需要定义一个名为“Observer”的"接口",其中包含必须要实现的方法,以便于Observable对象可以调用这些实现的方法(例如update)。在Python中,你可以直接传递普通函数或绑定方法作为观察者,而无需所有的样板代码。这并不是说你永远不应该像这样做。在Python中,“接口”也有自己的用途。 - Ilja Everilä
1
评论中提到的观点(需要带着一定的保留,确实...只有在某种程度上)是您不会像构建功能等效的Java代码那样构建Python代码。 - spectras
@IljaEverilä 我认为上面代码中的接口是为了强制要求观察者类中存在一个更新方法。如果没有接口,该如何实现这个强制要求呢? - anekix
1
通过不强制执行任何内容,而是直接接受可调用对象而不是“观察者对象”,甚至可以在Python中传递绑定方法。 - Ilja Everilä
显示剩余4条评论
1个回答

10

在Python中,您可以使用属性 描述符、自定义 __setattr__装饰器等多种方式来“观察”某些事物。

这里有一个使用一等函数的简单示例:

class Foo(object):
    def __init__(self):
        self.observers = []

    def register(self, fn):
        self.observers.append(fn)
        return fn   # <-- See comments below answer

    def notify_observers(self, *args, **kwargs):
        for fn in self.observers:
            fn(*args, **kwargs)

您可以注册任何可调用项。

class Bar(object):
    def do_something(self, *args, **kwargs):
        pass # do something

foo = Foo()
bar = Bar()
foo.register(bar.do_something)

这将正常工作。对do_something的调用将具有正确的self值。因为对象的方法是可调用对象,它们携带对它们绑定到的实例的引用。
这可能有助于了解它在后台是如何工作的:
>>> bar
<Bar object at 0x7f3fec4a5a58>
>>> bar.do_something
<bound method Bar.do_something of <Bar object at 0x7f3fec4a5a58>>
>>> type(bar.do_something)
<class 'method'>
>>> bar.do_something.__self__
<Bar object at 0x7f3fec4a5a58>

[编辑:装饰器示例]

您也可以像这样使用我们上面定义的register方法作为装饰器:

foo = Foo()

@foo.register
def do_something(*args, **kwargs):
    pass # do something

为了使其起作用,只需记住register需要返回它注册的可调用对象。

3
如果register()返回fn,那它也可以像装饰器一样工作,因此可以对函数进行@observable.register ...的操作。这只是Python可能与其他语言不同的一个小例子。 - Ilja Everilä
3
我担心这个答案会太多了,特别是因为它也是一种范式转换(在定义观察者方法之前需要存在可观测实例,这使得它与Java更加远离)。但这完全是真实的,我肯定会在实际示例中返回这个可调用对象。我加上了带注释的返回语句。 - spectras
我对那个装饰器解决方案很感兴趣。;) 我不是来自Java,而是C ++。 - buhtz
我完全同意这个答案。我想讨论的是:如果Foo是一个通用的基类,那么Foo.notify_observers不应该是私有的(Foo._notify_observers),这样notify_observers就不会成为派生类的公共API的一部分了。 - moi
1
@moi 在许多 API 中这是合理的。在这样一个玩具示例中,这并不重要。 - spectras
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接