私はpydispatcherを知っていますが、Python用の他のイベント関連パッケージが存在する必要があります。
どのライブラリが利用できますか?
大規模なフレームワークの一部であるイベントマネージャーには興味がありません。簡単に拡張できる小さな必要最低限のソリューションを使用したいと思います。
私はpydispatcherを知っていますが、Python用の他のイベント関連パッケージが存在する必要があります。
どのライブラリが利用できますか?
大規模なフレームワークの一部であるイベントマネージャーには興味がありません。簡単に拡張できる小さな必要最低限のソリューションを使用したいと思います。
2022 年 1 月の時点で、これらは PyPI で利用可能なイベント関連のパッケージで、最新のリリース日順に並べられています。
1.0.0
: 2021 年 8 月0.3.1
: 2021年6月4.5.0
: 2020年9月0.1.31
: 2020年8月1.0.1
: 2020 年 6 月2.0
: 2019年9月4.0.3
: 2019 年 1 月0.2.3a0
: 2018年0.0.5
: 20182.1.2
: 20170.0.7
: 20161.4
: 20152.0.5
: 20151.0
: 20120.3.1
: 2008非常に異なる用語 (イベント、シグナル、ハンドラー、メソッド ディスパッチ、フックなど) を使用して、多数のライブラリから選択できます。
上記のパッケージの概要と、ここでの回答に記載されている手法を維持しようとしています。
まず、いくつかの用語...
イベント システムの最も基本的なスタイルは、オブザーバー パターンの単純な実装である「ハンドラー メソッドのバッグ」です。
基本的に、ハンドラー メソッド (callables) は配列に格納され、イベントが「発生」したときにそれぞれ呼び出されます。
オブザーバー イベント システムの欠点は、実際の Event オブジェクト (またはハンドラー リスト) にしかハンドラーを登録できないことです。そのため、登録時にイベントがすでに存在している必要があります。
そのため、イベント システムの 2 番目のスタイルである パブリッシュ サブスクライブ パターンが存在します。ここでは、ハンドラーはイベント オブジェクト (またはハンドラー リスト) ではなく、中央のディスパッチャーに登録されます。また、通知者はディスパッチャとのみ通信します。何をリッスンするか、何をパブリッシュするかは、名前 (文字列) に過ぎない「シグナル」によって決定されます。
同様に興味深いかもしれません: Mediator パターン.
「フック」システムは通常、アプリケーション プラグインのコンテキストで使用されます。アプリケーションには固定の統合ポイント (フック) が含まれており、各プラグインはそのフックに接続して特定のアクションを実行できます。
注: threading.Eventは、上記の意味での「イベント システム」ではありません。これは、あるスレッドが別のスレッドが Event オブジェクトを「通知」するまで待機するスレッド同期システムです。
ネットワーク メッセージング ライブラリでは、「イベント」という用語もよく使用されます。これらは概念が似ている場合があります。時々そうではありません。もちろん、スレッド、プロセス、およびコンピューターの境界を横断できます。pyzmq、pymq、 Twisted、Tornado、 gevent、eventletなどを参照してください 。
Python では、メソッドまたはオブジェクトへの参照を保持することで、ガベージ コレクターによって削除されないようにします。これは望ましいことですが、メモリ リークにつながる可能性もあります。リンクされたハンドラがクリーンアップされることはありません。
一部のイベント システムでは、通常の参照ではなく弱参照を使用して、これを解決しています。
オブザーバー スタイルのイベント システム:
list
。set
代わりに aを使用し、どちらも妥当な追加である実装を使用します。list
__call__
pydispatch.Dispatcher
。パブリッシュ/サブスクライブ ライブラリ:
その他:
私はこのようにしてきました:
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はなく、署名もありません。これは本当にひどいものです。
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
私はzope.eventを使用します。それはあなたが想像できる最も素朴な骨です。:-)実際、完全なソースコードは次のとおりです。
subscribers = []
def notify(event):
for subscriber in subscribers:
subscriber(event)
たとえば、プロセス間でメッセージを送信することはできないことに注意してください。それはメッセージングシステムではなく、単なるイベントシステムであり、それ以上でもそれ以下でもありません。
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()
これは、正常に機能する最小限のデザインです。あなたがしなければならないことは、単に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')
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')
出力:
最初の敬礼
挨拶 オスカー
こんにちは オスカー
こんにちはオスカーの挨拶を削除します
EventManager コード:
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)
呼び出し先と呼び出し元の両方の署名も保証する、Longpoke の最小限のアプローチのバリエーションを作成しました。
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()
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"
pyQt でコードを作成する場合、QT ソケット/シグナル パラダイムを使用します。django も同様です。
非同期 I/OI を実行している場合は、ネイティブ選択モジュールを使用します
SAX python パーサーを使用している場合は、SAX が提供するイベント API を使用しています。だから、私は基礎となるAPIの犠牲者のようです:-)
イベントフレームワーク/モジュールに何を期待しているのかを自問する必要があるかもしれません。私の個人的な好みは、QT の Socket/Signal パラダイムを使用することです。それについての詳細は、ここで見つけることができます
考慮すべき別のモジュールを次に示します。より要求の厳しいアプリケーションには実行可能な選択肢のようです。
Py-notify は、Observer プログラミング パターンを実装するためのツールを提供する Python パッケージです。これらのツールには、シグナル、条件、および変数が含まれます。
シグナルは、シグナルが発行されたときに呼び出されるハンドラーのリストです。条件は基本的に、条件の状態が変化したときに発行されるシグナルと結合されたブール変数です。これらは、標準の論理演算子 (not、and など) を使用して複合条件に組み合わせることができます。変数は、条件とは異なり、ブール値だけでなく任意の Python オブジェクトを保持できますが、組み合わせることはできません。