懒惰事件在python中发布订阅

时间:2010-07-13 13:55:37

标签: python events lazy-loading publish-subscribe

我需要在谷歌应用引擎应用程序中使用事件消息系统。

我指的是以下python库。

http://pubsub.sourceforge.net/apidocs/concepts.html

我的问题是,是否必须将我想要执行的侦听器函数导入(或以其他方式存储)到执行路径的某个位置以便在事件上运行它?

有很多事件,我希望尽可能延迟加载。

可能是什么工作?

在python中是否有任何惰性事件发布订阅框架?

1 个答案:

答案 0 :(得分:1)

tipfy(特定于App-Engine的微框架)具有延迟加载,但仅适用于您的代码所服务的Web请求的特定“事件”。其他网络框架也有它,但是tipfy很小而且很简单,可以轻松地研究和模仿它的来源。

因此,如果由于“延迟加载”问题而无法找到更符合您口味的更丰富的事件框架,您可以选择一个需要注册/订阅可调用对象的框架,并允许字符串命名函数也要注册,就像tipfy一样。当然,如果需要为某个事件提供服务,那么这个命名的函数将被及时加载。

让我举一些简化的假设代码。假设您有一个包含以下内容的事件框架:

import collections
servers = collections.defaultdict(list)

def register(eventname, callable):
    servers[eventname].append(callable)

def raise(eventname, *a, **k):
    for s in servers.get(eventname, ()):
        s(*a, **k)

当然,任何现实世界的事件框架的内部都会更加丰富,但是这样的东西在它的最底层都是可以辨别的。

因此,这需要在注册时加载callable ......然而,即使不触及框架的内部,也可以轻松扩展它。考虑:

import sys

class LazyCall(object):
    def __init__(self, name):
        self.name = name
        self.f = None
    def __call__(self, *a, **k):
        if self.f is None:
            modname, funname = self.name.rsplit('.', 1)
            if modname not in sys.modules:
                __import__(modname)
            self.f = getattr(sys.modules[modname], funname)
        self.f(*a, **k)

当然,你需要更好的错误处理& c,但这是它的要点:将命名函数的字符串(例如'package.module.func')包装到一个知道如何懒惰地加载它的包装器对象中。现在,register(LazyCall('package.module.func'))将在未触及的框架中注册这样一个包装器 - 并根据请求延迟加载它。

这个用例,顺便说一句,可以作为Python习语的一个相当好的例子,一些愚蠢的傻瓜声称,大声和str,不存在,或不应该存在,或者某种东西:一个动态改变它的对象自己的班级。这个成语的用例是为两个状态之一中存在的对象“切割中间人”,从第一个到第二个的转换是不可逆转的。这里,懒惰调用者的第一个状态是“我知道函数的名称,但没有对象”,第二个是“我知道函数对象”。由于从第一个到第二个的移动是不可逆转的,如果你愿意,你可以减少每次测试的小开销(或Strategy设计模式的间接开销):

class _JustCallIt(object):
    def __call__(self, *a, **k):
        self.f(*a, **k)

class LazyCall(object):
    def __init__(self, name):
        self.name = name
        self.f = None
    def __call__(self, *a, **k):
        modname, funname = self.name.rsplit('.', 1)
        if modname not in sys.modules:
            __import__(modname)
        self.f = getattr(sys.modules[modname], funname)
        self.__class__ = _JustCallIt
        self.f(*a, **k)

这里的收益是适度的,因为它基本上只是从每次通话中削减一个if self.f is None:支票;但这是一个真正的收获,没有真正的缺点,除了导致先前命名的顽固的傻瓜fla到他们典型的愤怒和无意识的疯狂(如果你把 作为一个缺点)。

无论如何,实施选择取决于你,而不是我 - 或者,幸运的是,他们; - )。

一个设计选择:是否修补register本身直接接受字符串参数(并根据需要包装它们),基本上就像tipfy那样,或者在注册站点进行显式包装,离开register(或subscribe或者它被称为)原始。在这个特殊情况下,我没有通过“明确比隐含更好”的口头禅设定更多的权重,因为像

这样的事情
register(somevent, 'package.module.function')

一样明确
register(somevent, LazyCall('package.module.function'))

,即 非常清楚发生了什么,它可以说更清晰/更具可读性。

然而, 真的很好,显式包装方法不会触及底层框架:无论你在哪里传递函数,你现在都可以传递该函数的名称 (作为字符串命名包,模块和函数本身),无缝地。那么,如果我改造现有的框架,我会采用明确的方法。

最后,如果您想要注册不是函数的callables(例如)某些类的实例,或者这些实例的绑定方法,您可以将LazyCall丰富到LazyInstantiateAndCall&等变体中; c为此目的。当然,架构变得有点复杂(因为你想要实例化新对象识别已经存在的对象的方法),但是通过将这些工作委托给设计良好的工厂系统,它不应该坏。但是,我没有深入研究这样的改进,因为这个答案已经相当长了,无论如何,在许多情况下,简单的“命名函数”方法就足够了! - )