简单的 C# 风格的事件调度器
项目描述
前言
介绍
该库提供了一个简单的事件调度器,类似于 C# 语言提供的事件构造。该库没有外部依赖项。
它适用于用例,其中发出事件的组件和侦听事件的组件就事件的类型和与之相关的语义达成一致。例如,对于事件处理程序来说就是如此,只要某些属性的值发生变化,它就会侦听由 GUI 按钮对象发出的“单击”事件或由对象发出的通知。这与PyDispatcher所采用的方法不同,后者更通用,有利于弱耦合组件之间的通信。
兼容性
代码主要是为 Python 2.6 编写和测试的。众所周知,它可以与 Python 3.2 一起使用。它也应该与 2.5 兼容,但您可能必须在此处和那里插入一些from __future__ import with_statement 行(这通常未经测试)。它应该与 Python 的替代实现(如 Jython 或 IronPython)一起工作(尚未经过测试)。但请注意,此文件中定义的某些测试用例可能会由于不同的垃圾收集实现而失败;这个文件是用 CPython 编写的。
文档
基本用法
>>> from darts.lib.utils.event import Publisher, ReferenceRetention as RR >>> some_event = Publisher()
发布者是主要组件。它充当回调/侦听器的注册表。让我们定义一个监听器
>>> def printer(*event_args, **event_keys): ... print event_args, event_keys
为了接收通知,客户端必须订阅发布者。这可以很简单
>>> some_event.subscribe(printer) #doctest: +ELLIPSIS <SFHandle ...>
subscribe调用的结果是Subscription类(的某个子类)的一个实例。当不再需要通知时,可以稍后使用此值以取消订阅。实际的子类是您通常不应该关心的实现细节。您需要知道的(并且实际上可以依赖)是,它将是类Subscription的一个实例,并且它将提供已记录为该类的公共 API 的任何内容(现在:只有方法cancel) .
现在,让我们发出一个事件信号,看看会发生什么:
>>> some_event.publish('an-event') ('an-event',) {}
如您所见,打印机已收到该事件的通知,并将其参数适当地打印到控制台。
取消订阅
如前所述,调用subscribe的结果是一个特殊的订阅对象,它代表了监听器向发布者的注册。
>>> s1 = some_event.subscribe(printer) >>> some_event.publish('another-event') ('another-event',) {} ('another-event',) {} >>> s1.cancel() True >>> some_event.publish('yet-another-one') ('yet-another-one',) {}
发布者是完全可重入的。这意味着,您可以从侦听器中订阅事件,也可以在该上下文中取消订阅:
>>> def make_canceller(subs): ... def listener(*unused_1, **unused_2): ... print "Cancel", subs, subs.cancel() ... return listener >>> s1 = some_event.subscribe(printer) >>> s2 = some_event.subscribe(make_canceller(s1)) >>> some_event.publish('gotta-go') #doctest: +ELLIPSIS ('gotta-go',) {} ('gotta-go',) {} Cancel <SFHandle ...> True >>> some_event.publish('gone') #doctest: +ELLIPSIS ('gone',) {} Cancel <SFHandle ...> False >>> s1.cancel() False
取消调用的结果告诉我们,在调用之前订阅已经被撤消(通过我们的魔术取消监听器)。一般来说,多次调用cancel是无害的;除了第一个调用之外,所有调用都被忽略。
现在让我们移除神奇的 I-can-cancel-stuff 监听器并继续:
>>> s2.cancel() True
使用不可调用对象作为回调
每当我们在上面进行订阅时,我们实际上都会简化一些事情。该方法的完整签名是:
def subscribe(listener[, method[, reference_retention]])
让我们首先探索方法参数。到目前为止,我们只使用函数对象作为监听器。基本上,事实上,我们可能使用过任何可调用对象。请记住,任何对象在 Python 中都是“可调用的”,如果它提供了__call__方法,那么猜猜,方法参数的默认值是多少?
>>> s1 = some_event.subscribe(printer, method='__call__') >>> some_event.publish('foo') ('foo',) {} ('foo',) {} >>> s1.cancel() True
没什么新鲜的。所以,现在你可能会问:我什么时候使用不同的方法名?
>>> class Target(object): ... def __init__(self, name): ... self.name = name ... def _callback(self, *args, **keys): ... print self.name, args, keys >>> s1 = some_event.subscribe(Target('foo')) >>> some_event.publish('Bumm!') #doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: 'Target' object is not callable
哎呀。在有人注意到我们的错误之前,让我们删除违规者:
>>> s1.cancel() True >>> s1 = some_event.subscribe(Target('foo'), method='_callback') >>> some_event.publish('works!') ('works!',) {} foo ('works!',) {}
参考保留
所以,就是这样。不过,仍然有一个未探索的论点可以向左订阅 :reference_retention。这个名字听起来很危险,但它有什么作用呢?
>>> listener = Target('yummy') >>> s2 = some_event.subscribe(listener, method='_callback', reference_retention=RR.WEAK) >>> some_event.publish('yow') ('yow',) {} foo ('yow',) {} yummy ('yow',) {}
嗯。到目前为止,没有任何区别。让我们做一个简单的改变:
>>> listener = None >>> some_event.publish('yow') ('yow',) {} foo ('yow',) {}
啊。好的。我们可爱的听众不见了。发生了什么?好吧,通过指定WEAK的引用保留策略,我们告诉发布者,它应该使用对刚刚安装的侦听器的弱引用,而不是默认的强引用。在我们通过将listener设置为None来发布唯一已知的对 listener 的强引用之后,侦听器实际上已从发布者中删除。请注意,顺便说一句,由于垃圾收集的不同策略,上述示例可能会在 CPython 以外的 python 实现中失败。不过,该原则在 Jython 和 IronPython 中应该仍然有效,但在这些实现中,不能保证一旦删除了对它的最后一个引用,侦听器就会被删除。
当然,如果要调用的方法是默认方法,这一切也都有效:__call__:
>>> def make_listener(name): ... def listener(*args, **keys): ... print name, args, keys ... return listener >>> listener = make_listener('weak') >>> s2 = some_event.subscribe(listener, reference_retention=RR.WEAK) >>> some_event.publish('event') ('event',) {} foo ('event',) {} weak ('event',) {} >>> listener = None >>> some_event.publish('event') ('event',) {} foo ('event',) {}
这就是关于图书馆的所有知识。正如我上面所说的:它很简单,可能并不适用于所有场景和用例,但它完成了它被写入的内容。
错误处理
Publisher类不打算被子类化。如果您需要定制行为,您可以使用传递给构造函数的策略对象/回调。目前,只有一个可调整的策略,即发布者在监听器引发异常时的行为:
>>> def toobad(event): ... if event == 'raise': ... raise ValueError >>> s1 = some_event.subscribe(toobad) >>> some_event.publish('harmless') ('harmless',) {} foo ('harmless',) {} >>> some_event.publish('raise') Traceback (most recent call last): ... ValueError
如您所见,默认行为是在publish中重新引发异常。根据用例,这可能还不够。特别是,它将阻止以后注册的任何侦听器运行。所以,让我们定义我们自己的错误处理:
>>> def log_error(exception, value, traceback, subscription, args, keys): ... print "caught", exception >>> publisher = Publisher(exception_handler=log_error) >>> publisher.subscribe(toobad) #doctest: +ELLIPSIS <SFHandle ...> >>> publisher.subscribe(printer) #doctest: +ELLIPSIS <SFHandle ...> >>> publisher.publish('harmless') ('harmless',) {} >>> publisher.publish('raise') caught <type 'exceptions.ValueError'> ('raise',) {}
作为在构造时提供错误处理程序的替代方法,您还可以在发布事件时提供错误处理程序,如下所示:
>>> def log_error_2(exception, value, traceback, subscription, args, keys): ... print "caught", exception, "during publication" >>> publisher.publish_safely(log_error_2, 'raise') caught <type 'exceptions.ValueError'> during publication ('raise',) {}
如您所见,每次调用错误处理程序优先于发布者的默认错误处理程序。请注意,没有链接,即,如果每次调用错误处理程序引发异常,则不会调用发布者的默认处理程序,而是将异常简单地向外传播到publish_safely的调用者:发布者无法区分因为处理程序想要中止调度而引发的异常和意外引发的异常,因此处理程序引发的所有异常都简单地转发到客户端应用程序。
线程安全
该库是完全线程感知和线程安全的。因此,订阅跨多个线程共享的侦听器是安全的,取消订阅也是如此。
变化
0.4版
订阅句柄现在提供对其侦听器对象和方法名称的访问。这是为了错误处理代码而添加的,它想要记录异常并提供一种更好的方式来识别真正的监听器,它是流氓。
0.3版
修复setup.py以正确声明使用的命名空间包。
0.2版
错误处理已更改。现在,默认异常处理程序不是子类化发布者,而是在构造期间作为回调传递给发布者。Publisher类现在被记录为“不打算被子类化”。
项目详情
darts.util.events -0.4.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | bf0a70d8d67af15c95e62f5596c97cdf76dc068bb1cdae0f9a4094eb5ee33579 |
|
MD5 | bea09eaef990f7fdc53850d5cb06c617 |
|
布莱克2-256 | 0f5e81fefc75f670847d2d495662cb20e5531d7d43ed126441f44bbe27aad43a |