您使用什么Python事件系统?我已经知道pydispatcher,但我想知道还能找到什么,或者常用?
我对作为大型框架一部分的事件管理器不感兴趣,我宁愿使用一个我可以轻松扩展的小型简单解决方案。
答案 0 :(得分:122)
结束答案中提到的各种事件系统:
最基本的事件系统风格是“处理程序方法包”,它是Observer pattern的简单实现。基本上,处理程序方法(callables)存储在一个数组中,并在事件“触发”时被调用。
list
可以非常简单地实现这样的事件系统。set
代替list
来存储行李,并实施__call__
这些都是合理的补充。这些事件系统的缺点是您只能在实际的Event对象(或处理程序列表)上注册处理程序。 因此,在注册时,事件已经存在。
这就是第二种事件系统风格存在的原因:publish-subscribe pattern。 这里,处理程序不在事件对象(或处理程序列表)上注册,而是在中央调度程序上注册。通知程序也只与调度员交谈。听什么或发布什么是由'信号'决定的,这只不过是一个名字(字符串)。
QObject
的类的对象。注意:threading.Event不是上述意义上的“事件系统”。它是一个线程同步系统,其中一个线程等待,直到另一个线程“发出信号”Event对象。
注意:上面尚未包括pypydispatcher,python-dispatch,pluggy的'钩子系统'也可能会引起关注。
答案 1 :(得分:88)
我一直这样做:
class Event(list):
"""Event subscription.
A list of callable objects. Calling an instance of this will cause a
call to each item in the list in ascending order by index.
Example Usage:
>>> def f(x):
... print 'f(%s)' % x
>>> def g(x):
... print 'g(%s)' % x
>>> e = Event()
>>> e()
>>> e.append(f)
>>> e(123)
f(123)
>>> e.remove(f)
>>> e()
>>> e += (f, g)
>>> e(10)
f(10)
g(10)
>>> del e[0]
>>> e(2)
g(2)
"""
def __call__(self, *args, **kwargs):
for f in self:
f(*args, **kwargs)
def __repr__(self):
return "Event(%s)" % list.__repr__(self)
但是,和我见过的其他所有内容一样,没有自动生成的pydoc,也没有签名,这真的很糟糕。
答案 2 :(得分:63)
我们使用Michael Foord在其Event Pattern:
中建议的EventHook只需使用以下命令将EventHooks添加到您的课程中:
class MyBroadcaster()
def __init__():
self.onChange = EventHook()
theBroadcaster = MyBroadcaster()
# add a listener to the event
theBroadcaster.onChange += myFunction
# remove listener from the event
theBroadcaster.onChange -= myFunction
# fire event
theBroadcaster.onChange.fire()
我们添加了从对象中删除所有侦听器到Michaels类的功能,最后得到了这个:
class EventHook(object):
def __init__(self):
self.__handlers = []
def __iadd__(self, handler):
self.__handlers.append(handler)
return self
def __isub__(self, handler):
self.__handlers.remove(handler)
return self
def fire(self, *args, **keywargs):
for handler in self.__handlers:
handler(*args, **keywargs)
def clearObjectHandlers(self, inObject):
for theHandler in self.__handlers:
if theHandler.im_self == inObject:
self -= theHandler
答案 3 :(得分:17)
我使用zope.event。这是你能想象到的最无聊的骨头。 :-) 实际上,这是完整的源代码:
subscribers = []
def notify(event):
for subscriber in subscribers:
subscriber(event)
请注意,例如,您无法在进程之间发送消息。它不是一个消息系统,只是一个事件系统,仅此而已。
答案 4 :(得分:13)
我在Valued Lessons找到了这个小脚本。它似乎具有我所追求的正确的简单/功率比。 Peter Thatcher是以下代码的作者(未提及许可)。
class Event:
def __init__(self):
self.handlers = set()
def handle(self, handler):
self.handlers.add(handler)
return self
def unhandle(self, handler):
try:
self.handlers.remove(handler)
except:
raise ValueError("Handler is not handling this event, so cannot unhandle it.")
return self
def fire(self, *args, **kargs):
for handler in self.handlers:
handler(*args, **kargs)
def getHandlerCount(self):
return len(self.handlers)
__iadd__ = handle
__isub__ = unhandle
__call__ = fire
__len__ = getHandlerCount
class MockFileWatcher:
def __init__(self):
self.fileChanged = Event()
def watchFiles(self):
source_path = "foo"
self.fileChanged(source_path)
def log_file_change(source_path):
print "%r changed." % (source_path,)
def log_file_change2(source_path):
print "%r changed!" % (source_path,)
watcher = MockFileWatcher()
watcher.fileChanged += log_file_change2
watcher.fileChanged += log_file_change
watcher.fileChanged -= log_file_change2
watcher.watchFiles()
答案 5 :(得分:8)
您可以查看pymitter(pypi)。它是一个小的单文件(~250 loc)方法 “提供名称空间,通配符和TTL”。
这是一个基本的例子:
from pymitter import EventEmitter
ee = EventEmitter()
# decorator usage
@ee.on("myevent")
def handler1(arg):
print "handler1 called with", arg
# callback usage
def handler2(arg):
print "handler2 called with", arg
ee.on("myotherevent", handler2)
# emit
ee.emit("myevent", "foo")
# -> "handler1 called with foo"
ee.emit("myotherevent", "bar")
# -> "handler2 called with bar"
答案 6 :(得分:6)
我创建了一个EventManager
类(最后的代码)。语法如下:
#Create an event with no listeners assigned to it
EventManager.addEvent( eventName = [] )
#Create an event with listeners assigned to it
EventManager.addEvent( eventName = [fun1, fun2,...] )
#Create any number event with listeners assigned to them
EventManager.addEvent( eventName1 = [e1fun1, e1fun2,...], eventName2 = [e2fun1, e2fun2,...], ... )
#Add or remove listener to an existing event
EventManager.eventName += extra_fun
EventManager.eventName -= removed_fun
#Delete an event
del EventManager.eventName
#Fire the event
EventManager.eventName()
这是一个例子:
def hello(name):
print "Hello {}".format(name)
def greetings(name):
print "Greetings {}".format(name)
EventManager.addEvent( salute = [greetings] )
EventManager.salute += hello
print "\nInitial salute"
EventManager.salute('Oscar')
print "\nNow remove greetings"
EventManager.salute -= greetings
EventManager.salute('Oscar')
输出:
最初的敬礼
奥斯卡的问候 你好奥斯卡现在删除问候语 你好奥斯卡
EventManger代码:
class EventManager:
class Event:
def __init__(self,functions):
if type(functions) is not list:
raise ValueError("functions parameter has to be a list")
self.functions = functions
def __iadd__(self,func):
self.functions.append(func)
return self
def __isub__(self,func):
self.functions.remove(func)
return self
def __call__(self,*args,**kvargs):
for func in self.functions : func(*args,**kvargs)
@classmethod
def addEvent(cls,**kvargs):
"""
addEvent( event1 = [f1,f2,...], event2 = [g1,g2,...], ... )
creates events using **kvargs to create any number of events. Each event recieves a list of functions,
where every function in the list recieves the same parameters.
Example:
def hello(): print "Hello ",
def world(): print "World"
EventManager.addEvent( salute = [hello] )
EventManager.salute += world
EventManager.salute()
Output:
Hello World
"""
for key in kvargs.keys():
if type(kvargs[key]) is not list:
raise ValueError("value has to be a list")
else:
kvargs[key] = cls.Event(kvargs[key])
cls.__dict__.update(kvargs)
答案 7 :(得分:5)
这是一个应该运行良好的最小设计。您要做的就是在类中继承Observer
,然后使用observe(event_name, callback_fn)
来监听特定事件。只要在代码中的任何地方触发该特定事件(即。Event('USB connected')
),就会触发相应的回调。
class Observer():
_observers = []
def __init__(self):
self._observers.append(self)
self._observed_events = []
def observe(self, event_name, callback_fn):
self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})
class Event():
def __init__(self, event_name, *callback_args):
for observer in Observer._observers:
for observable in observer._observed_events:
if observable['event_name'] == event_name:
observable['callback_fn'](*callback_args)
示例:
class Room(Observer):
def __init__(self):
print("Room is ready.")
Observer.__init__(self) # DON'T FORGET THIS
def someone_arrived(self, who):
print(who + " has arrived!")
# Observe for specific event
room = Room()
room.observe('someone arrived', room.someone_arrived)
# Fire some events
Event('someone left', 'John')
Event('someone arrived', 'Lenard') # will output "Lenard has arrived!"
Event('someone Farted', 'Lenard')
答案 8 :(得分:5)
我做了一个Longpoke简约方法的变体,也确保了callees和来电者的签名:
class EventHook(object):
'''
A simple implementation of the Observer-Pattern.
The user can specify an event signature upon inizializazion,
defined by kwargs in the form of argumentname=class (e.g. id=int).
The arguments' types are not checked in this implementation though.
Callables with a fitting signature can be added with += or removed with -=.
All listeners can be notified by calling the EventHook class with fitting
arguments.
>>> event = EventHook(id=int, data=dict)
>>> event += lambda id, data: print("%d %s" % (id, data))
>>> event(id=5, data={"foo": "bar"})
5 {'foo': 'bar'}
>>> event = EventHook(id=int)
>>> event += lambda wrong_name: None
Traceback (most recent call last):
...
ValueError: Listener must have these arguments: (id=int)
>>> event = EventHook(id=int)
>>> event += lambda id: None
>>> event(wrong_name=0)
Traceback (most recent call last):
...
ValueError: This EventHook must be called with these arguments: (id=int)
'''
def __init__(self, **signature):
self._signature = signature
self._argnames = set(signature.keys())
self._handlers = []
def _kwargs_str(self):
return ", ".join(k+"="+v.__name__ for k, v in self._signature.items())
def __iadd__(self, handler):
params = inspect.signature(handler).parameters
valid = True
argnames = set(n for n in params.keys())
if argnames != self._argnames:
valid = False
for p in params.values():
if p.kind == p.VAR_KEYWORD:
valid = True
break
if p.kind not in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY):
valid = False
break
if not valid:
raise ValueError("Listener must have these arguments: (%s)"
% self._kwargs_str())
self._handlers.append(handler)
return self
def __isub__(self, handler):
self._handlers.remove(handler)
return self
def __call__(self, *args, **kwargs):
if args or set(kwargs.keys()) != self._argnames:
raise ValueError("This EventHook must be called with these " +
"keyword arguments: (%s)" % self._kwargs_str())
for handler in self._handlers[:]:
handler(**kwargs)
def __repr__(self):
return "EventHook(%s)" % self._kwargs_str()
答案 9 :(得分:3)
如果我在pyQt中编码,我使用QT套接字/信号范例,同样适用于django
如果我正在进行异步I / O,我使用本机选择模块
如果我使用SAX python解析器,我正在使用SAX提供的事件API。所以看起来我是底层API的受害者: - )
也许您应该问自己对事件框架/模块的期望是什么。我个人的偏好是使用QT的Socket / Signal范例。有关这方面的更多信息,请访问here
答案 10 :(得分:2)
这是另一个module供考虑。对于要求更高的应用来说,这似乎是一个可行的选择。
Py-notify是一个Python包 提供实施工具 观察者编程模式。这些 工具包括信号,条件和 变量
信号是处理程序的列表 在发出信号时调用。 条件基本上是布尔值 变量加上一个信号 条件状态时发出 变化。它们可以组合使用 标准逻辑运算符(不是,和, 等)进入复合条件。 与条件不同,变量可以容纳 任何Python对象,而不仅仅是布尔值, 但他们不能合并。
答案 11 :(得分:1)
如果您需要跨流程或网络边界运行的事件总线,可以尝试PyMQ。它目前支持发布/订阅,消息队列和同步RPC。默认版本在Redis后端之上运行,因此您需要一个正在运行的Redis服务器。还有一个用于测试的内存后端。您还可以编写自己的后端。
import pymq
# common code
class MyEvent:
pass
# subscribe code
@pymq.subscriber
def on_event(event: MyEvent):
print('event received')
# publisher code
pymq.publish(MyEvent())
# you can also customize channels
pymq.subscribe(on_event, channel='my_channel')
pymq.publish(MyEvent(), channel='my_channel')
要初始化系统:
from pymq.provider.redis import RedisConfig
# starts a new thread with a Redis event loop
pymq.init(RedisConfig())
# main application control loop
pymq.shutdown()
免责声明:我是该图书馆的作者
答案 12 :(得分:0)
如果你想做更复杂的事情,比如合并事件或重试,你可以使用Observable模式和一个实现它的成熟库。 https://github.com/ReactiveX/RxPY。 Observable在Javascript和Java中非常常见,并且非常便于用于某些异步任务。
from rx import Observable, Observer
def push_five_strings(observer):
observer.on_next("Alpha")
observer.on_next("Beta")
observer.on_next("Gamma")
observer.on_next("Delta")
observer.on_next("Epsilon")
observer.on_completed()
class PrintObserver(Observer):
def on_next(self, value):
print("Received {0}".format(value))
def on_completed(self):
print("Done!")
def on_error(self, error):
print("Error Occurred: {0}".format(error))
source = Observable.create(push_five_strings)
source.subscribe(PrintObserver())
<强>输出强>:
Received Alpha
Received Beta
Received Gamma
Received Delta
Received Epsilon
Done!
答案 13 :(得分:0)
您可以尝试buslane
模块。
此库使基于消息的系统的实现更加容易。它支持命令(单个处理程序)和事件(0或多个处理程序)方法。 Buslane使用Python类型注释来正确注册处理程序。
简单的例子:
from dataclasses import dataclass
from buslane.commands import Command, CommandHandler, CommandBus
@dataclass(frozen=True)
class RegisterUserCommand(Command):
email: str
password: str
class RegisterUserCommandHandler(CommandHandler[RegisterUserCommand]):
def handle(self, command: RegisterUserCommand) -> None:
assert command == RegisterUserCommand(
email='john@lennon.com',
password='secret',
)
command_bus = CommandBus()
command_bus.register(handler=RegisterUserCommandHandler())
command_bus.execute(command=RegisterUserCommand(
email='john@lennon.com',
password='secret',
))
要安装Buslane,只需使用pip:
$ pip install buslane
答案 14 :(得分:0)
前一段时间,我编写了可能对您有用的库。 它允许您拥有本地和全局侦听器,多种不同的注册方式,执行优先级等。
from pyeventdispatcher import register
register("foo.bar", lambda event: print("second"))
register("foo.bar", lambda event: print("first "), -100)
dispatch(Event("foo.bar", {"id": 1}))
# first second
答案 15 :(得分:0)
另一个方便的软件包是events。它封装了事件订阅和事件触发的核心,感觉就像是该语言的“自然”部分。它似乎与C#语言相似,后者提供了一种方便的方法来声明,订阅和触发事件。从技术上讲,事件是一个“槽”,可以将回调函数(事件处理程序)附加到该槽上,该过程称为订阅事件。
# Define a callback function
def something_changed(reason):
print "something changed because %s" % reason
# Use events module to create an event and register one or more callback functions
from events import Events
events = Events()
events.on_change += something_changed
触发事件时,将依次调用所有附加的事件处理程序。要触发事件,请在广告位上执行呼叫:
events.on_change('it had to happen')
这将输出:
'something changed because it had to happen'
更多文档可在github repo或documentation中找到。