어떤 이벤트 시스템을 파이썬으로 사용하십니까? 나는 이미 pydispatcher를 알고 있지만 다른 것을 발견하거나 일반적으로 사용하는 것이 궁금합니다.
큰 프레임 워크의 일부인 이벤트 관리자에는 관심이 없으며 쉽게 확장 할 수있는 작은 베어 본 솔루션을 사용하고 싶습니다.
어떤 이벤트 시스템을 파이썬으로 사용하십니까? 나는 이미 pydispatcher를 알고 있지만 다른 것을 발견하거나 일반적으로 사용하는 것이 궁금합니다.
큰 프레임 워크의 일부인 이벤트 관리자에는 관심이 없으며 쉽게 확장 할 수있는 작은 베어 본 솔루션을 사용하고 싶습니다.
답변:
2020 년 6 월 현재 PyPI에서 제공되는 이벤트 관련 패키지는 최신 출시 날짜순으로 정렬됩니다.
1.0.1
: 2020 년 6 월0.13.1
: 2020 년 6 월 (베타)2.0
: 2019 년 9 월0.1.2
: 2019 년 2 월4.0.3
: 2019 년 1 월4.4
: 20180.2.3a0
: 20180.0.5
: 20182.1.2
: 20170.0.7
: 20161.4
: 20152.0.5
: 20150.2.3
: 20141.0
: 20120.3.1
: 2008그것은 매우 다른 용어 (이벤트, 신호, 핸들러, 메소드 디스패치, 후크 등)를 사용하여 선택할 수있는 많은 라이브러리입니다.
위 패키지에 대한 개요와 여기 답변에 언급 된 기술을 유지하려고합니다.
먼저, 일부 용어는 ...
이벤트 시스템의 가장 기본적인 스타일은 Observer 패턴 의 간단한 구현 인 '핸들러 메서드 백' 입니다 .
기본적으로 핸들러 메소드 (호출 가능)는 배열에 저장되며 이벤트가 발생할 때 각각 호출됩니다.
관찰자 이벤트 시스템의 단점은 실제 이벤트 오브젝트 (또는 핸들러 목록)에서만 핸들러를 등록 할 수 있다는 것입니다. 따라서 등록시 이벤트가 이미 존재해야합니다.
그렇기 때문에 두 번째 스타일의 이벤트 시스템 인 publish-subscribe 패턴 이 존재합니다 . 여기서 핸들러는 이벤트 오브젝트 (또는 핸들러 목록)가 아니라 중앙 디스패처에 등록됩니다. 또한 알리미는 운영자와 만 대화합니다. 듣거나 게시 할 내용은 이름 (문자열)에 지나지 않는 '신호'에 의해 결정됩니다.
또한 관심을 가질만한 것은 Mediator pattern 입니다.
'후크'시스템은 응용 프로그램 플러그인의 맥락에서 일반적으로 사용됩니다. 응용 프로그램에는 고정 통합 지점 (후크)이 포함되어 있으며 각 플러그인은 해당 후크에 연결하여 특정 작업을 수행 할 수 있습니다.
참고 : 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도없고 서명도 없습니다.
_bag_of_handlers
이것을 목록 인 인스턴스 변수에 넣겠습니다 . 클래스의 add 메소드는 단순히입니다 self._bag_of_handlers.append(some_callable)
. 클래스의 fire 메소드는 제공된 args와 kwargs를 핸들러에 전달하여 _bag_of_handlers를 통해 루프하고 순차적으로 실행합니다.
Michael Foord가 제안한 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
self.__handlers = [h for h in self._handlers if getattr(h, 'im_self', False) != obj]
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')
산출:
첫 인사
인사말 오스카
안녕하세요 오스카인사말 삭제
안녕하세요 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)
당신은 pymitter ( pypi)를 볼 수 있습니다 )를 . "단일 네임 스페이스, 와일드 카드 및 TTL 제공"의 작은 단일 파일 (~ 250 개 위치) 접근 방식입니다.
기본 예는 다음과 같습니다.
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"
나는 수신자와 발신자 모두의 서명을 보장하는 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()
고려해야 할 다른 모듈 이 있습니다. 보다 까다로운 응용 분야에 적합한 선택 인 것 같습니다.
Py-notify는 Observer 프로그래밍 패턴을 구현하기위한 도구를 제공하는 Python 패키지입니다. 이러한 도구에는 신호, 조건 및 변수가 포함됩니다.
신호는 신호가 방출 될 때 호출되는 핸들러 목록입니다. 조건은 기본적으로 조건 상태가 변경 될 때 방출되는 신호와 결합 된 부울 변수입니다. 표준 논리 연산자 (not 등)를 사용하여 복합 조건으로 결합 할 수 있습니다. 조건과 달리 변수는 부울뿐만 아니라 모든 Python 객체를 보유 할 수 있지만 결합 할 수는 없습니다.
이벤트 병합 또는 재시 도와 같이보다 복잡한 작업을 수행하려면 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!
당신은 프로세스 또는 네트워크 경계를 넘어 작동하는 eventbus이 필요한 경우 시도 할 수 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()
면책 조항 : 나는이 도서관의 저자입니다
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
얼마 전에 저는 여러분에게 유용한 라이브러리를 작성했습니다. 로컬 및 글로벌 리스너, 여러 가지 등록 방법, 실행 우선 순위 등을 가질 수 있습니다.
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
모양의이 pyeventdispatcher을