0

我的谷歌应用引擎应用程序需要一个事件消息系统。

我指的是以下python库。

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

我的问题是,我要执行的侦听器函数是否必须导入(或以其他方式存在)到执行路径的某处才能在事件上运行它?

有很多事件,我想让它尽可能延迟加载。

可以解决什么问题?

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

4

1 回答 1

1

tipfy(一个 App-Engine 特定的微框架)具有延迟加载,但仅适用于您的代码正在服务的 Web 请求的特定“事件”。其他 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)

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

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

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 习语的一个相当好的例子,一些顽固的傻瓜大声而刺耳地声称,不存在或不应该存在,或者什么:一个对象动态地改变它自己的类。这个习语的用例是为存在于两种状态之一的对象“切断中间人”,从第一种状态到第二种状态的转换是不可逆的。在这里,惰性调用者的第一个状态是“我知道函数名但没有对象”,第二个状态是“我知道函数对象”。由于从第一个移动到第二个是不可逆的,如果您愿意,您可以减少每次测试的小开销(或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:从每次调用中减少了一次检查;但这是一个真正的收获,没有真正的缺点,除了导致前面提到的顽固的傻瓜陷入他们典型的愤怒和盲目的狂热(如果你把它算作缺点的话)。

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

作为一种设计选择:是否修补register自身以直接接受字符串参数(并根据需要包装它们),基本上是tipfy这样,或者在注册站点进行显式包装,留下registersubscribe或无论如何称为)原始。在这种特殊情况下,我并没有对“显式优于隐式”的口头禅给予太多重视,因为类似

register(somevent, 'package.module.function')

非常明确

register(somevent, LazyCall('package.module.function'))

即,清楚发生了什么,并且可以说它更干净/更具可读性。

尽管如此,显式包装方法使底层框架保持不变真的很好:无论您可以传递一个函数,您现在都可以无缝地传递该函数的名称(作为命名包、模块和函数本身的字符串)。所以,如果我改造现有框架,我会选择显式方法。

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

于 2010-07-13T14:44:59.983 回答