我的谷歌应用引擎应用程序需要一个事件消息系统。
我指的是以下python库。
http://pubsub.sourceforge.net/apidocs/concepts.html
我的问题是,我要执行的侦听器函数是否必须导入(或以其他方式存在)到执行路径的某处才能在事件上运行它?
有很多事件,我想让它尽可能延迟加载。
可以解决什么问题?
python中是否有任何惰性事件发布订阅框架?
我的谷歌应用引擎应用程序需要一个事件消息系统。
我指的是以下python库。
http://pubsub.sourceforge.net/apidocs/concepts.html
我的问题是,我要执行的侦听器函数是否必须导入(或以其他方式存在)到执行路径的某处才能在事件上运行它?
有很多事件,我想让它尽可能延迟加载。
可以解决什么问题?
python中是否有任何惰性事件发布订阅框架?
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
这样,或者在注册站点进行显式包装,留下register
(subscribe
或无论如何称为)原始。在这种特殊情况下,我并没有对“显式优于隐式”的口头禅给予太多重视,因为类似
register(somevent, 'package.module.function')
非常明确
register(somevent, LazyCall('package.module.function'))
即,很清楚发生了什么,并且可以说它更干净/更具可读性。
尽管如此,显式包装方法使底层框架保持不变真的很好:无论您可以传递一个函数,您现在都可以无缝地传递该函数的名称(作为命名包、模块和函数本身的字符串)。所以,如果我改造现有框架,我会选择显式方法。
最后,如果您想注册不是函数而是(例如)某些类的实例或此类实例的绑定方法的可调用对象,您可以为此目的丰富LazyCall
到诸如LazyInstantiateAndCall
&c 之类的变体。当然,架构变得有点复杂(例如,因为您想要实例化新对象的方法和识别现有对象的方法),但是通过将这项工作委托给设计良好的工厂系统,它不应该太复杂坏的。但是,我并没有深入了解此类改进,因为这个答案已经很长了,而且无论如何,在许多情况下,简单的“命名函数”方法就足够了!-)