[python] 파이썬의 이벤트 시스템

어떤 이벤트 시스템을 파이썬으로 사용하십니까? 나는 이미 pydispatcher를 알고 있지만 다른 것을 발견하거나 일반적으로 사용하는 것이 궁금합니다.

큰 프레임 워크의 일부인 이벤트 관리자에는 관심이 없으며 쉽게 확장 할 수있는 작은 베어 본 솔루션을 사용하고 싶습니다.



답변

PyPI 패키지

2020 년 6 월 현재 PyPI에서 제공되는 이벤트 관련 패키지는 최신 출시 날짜순으로 정렬됩니다.

더있다

그것은 매우 다른 용어 (이벤트, 신호, 핸들러, 메소드 디스패치, 후크 등)를 사용하여 선택할 수있는 많은 라이브러리입니다.

위 패키지에 대한 개요와 여기 답변에 언급 된 기술을 유지하려고합니다.

먼저, 일부 용어는 …

관찰자 패턴

이벤트 시스템의 가장 기본적인 스타일은 Observer 패턴 의 간단한 구현 인 ‘핸들러 메서드 백’ 입니다 .

기본적으로 핸들러 메소드 (호출 가능)는 배열에 저장되며 이벤트가 발생할 때 각각 호출됩니다.

발행-구독

관찰자 이벤트 시스템의 단점은 실제 이벤트 오브젝트 (또는 핸들러 목록)에서만 핸들러를 등록 할 수 있다는 것입니다. 따라서 등록시 이벤트가 이미 존재해야합니다.

그렇기 때문에 두 번째 스타일의 이벤트 시스템 인 publish-subscribe 패턴 이 존재합니다
. 여기서 핸들러는 이벤트 오브젝트 (또는 핸들러 목록)가 아니라 중앙 디스패처에 등록됩니다. 또한 알리미는 운영자와 만 대화합니다. 듣거나 게시 할 내용은 이름 (문자열)에 지나지 않는 ‘신호’에 의해 결정됩니다.

중재자 패턴

또한 관심을 가질만한 것은 Mediator pattern 입니다.

갈고리

‘후크’시스템은 응용 프로그램 플러그인의 맥락에서 일반적으로 사용됩니다. 응용 프로그램에는 고정 통합 지점 (후크)이 포함되어 있으며 각 플러그인은 해당 후크에 연결하여 특정 작업을 수행 할 수 있습니다.

다른 ‘이벤트’

참고 : threading.Event 는 위의 의미에서 ‘이벤트 시스템’이 아닙니다. 하나의 스레드가 다른 스레드가 Event 객체를 ‘신호’할 때까지 기다리는 스레드 동기화 시스템입니다.

네트워크 메시징 라이브러리는 종종 ‘이벤트’라는 용어를 사용합니다. 때때로 이들은 개념 상 유사하다. 때로는 그렇지 않습니다. 물론 스레드, 프로세스 및 컴퓨터 경계를 통과 할 수 있습니다. 예를 들어
pyzmq , pymq ,
Twisted , Tornado , gevent , eventlet을 참조하십시오 .

약한 참조

Python에서 메소드 또는 객체에 대한 참조를 보유하면 가비지 수집기에 의해 삭제되지 않습니다. 이것은 바람직 할 수 있지만 메모리 누수가 발생할 수 있습니다. 연결된 처리기는 정리되지 않습니다.

일부 이벤트 시스템은이를 해결하기 위해 일반 시스템 대신 약한 참조를 사용합니다.

다양한 라이브러리에 대한 몇 마디

관찰자 스타일의 이벤트 시스템 :

  • zope.event 는 이것이 어떻게 작동하는지의 핵심을 보여줍니다 ( Lenart ‘s answer 참조 ). 참고 :이 예제는 핸들러 인수도 지원하지 않습니다.
  • LongPoke의 ‘호출 가능 목록’ 구현은 이러한 이벤트 시스템이 서브 클래 싱을 통해 매우 최소한으로 구현 될 수 있음을 보여줍니다 list.
  • Felk의 변형 EventHook 은 또한 수신자 및 발신자의 서명을 보장합니다.
  • spassig의 EventHook (Michael Foord의 이벤트 패턴)은 간단한 구현입니다.
  • Josip의 소중한 수업 이벤트 클래스 는 기본적으로 동일하지만 a set대신 a list를 사용하여 가방을 저장하고 __call__둘 다 합리적인 추가 기능을 구현 합니다.
  • PyNotify 는 개념 상 유사하며 추가 변수 및 조건 개념 ( ‘변수 변경 이벤트’)을 제공합니다. 홈페이지가 작동하지 않습니다.
  • axel 는 기본적으로 스레딩, 오류 처리 등과 관련된 더 많은 기능을 갖춘 처리기입니다.
  • python-dispatch 에는 짝수 소스 클래스가 필요합니다 pydispatch.Dispatcher.
  • buslane 은 클래스 기반이며 단일 또는 다중 핸들러를 지원하며 광범위한 유형 힌트를 지원합니다.
  • Pithikos의 관찰자 / 이벤트 는 가벼운 디자인입니다.

발행-구독 라이브러리 :

  • 깜박임 에는 자동 연결 해제 및 발신자 기반 필터링과 같은 멋진 기능이 있습니다.
  • PyPubSub 는 안정적인 패키지이며 “주제와 메시지의 디버깅 및 유지 관리를 용이하게하는 고급 기능”을 약속합니다.
  • pymitter 는 Node.js EventEmitter2의 Python 포트이며 네임 스페이스, 와일드 카드 및 TTL을 제공합니다.
  • PyDispatcher 는 다 대다 출판 등의 측면에서 유연성을 강조하는 것 같습니다. 약한 참조를 지원합니다.
  • louie 는 재 작업 된 PyDispatcher이며 “다양한 상황에서”작동해야합니다.
  • pypydispatcherPyDispatcher 를 기반으로하며 PyPy에서도 작동합니다.
  • django.dispatch 는 재 작성된 PyDispatcher “인터페이스가 더 제한적이지만 성능이 더 뛰어납니다”.
  • pyeventdispatcher 는 PHP의 Symfony 프레임 워크의 이벤트 디스패처를 기반으로합니다.
  • dispatcher 는 django.dispatch에서 추출되었지만 상당히 오래되었습니다.
  • Cristian Garcia의 EventManger 는 실제로 짧은 구현입니다.

기타 :

  • pluggy 에는 pytest플러그인에서 사용하는 후크 시스템이 포함되어 있습니다.
  • RxPy3 는 Observable 패턴을 구현하고 이벤트 병합, 재시도 등을 허용합니다.
  • Qt의 신호 및 슬롯은 PyQt
    또는 PySide2 에서 사용할 수 있습니다 . 동일한 스레드에서 사용될 때 콜백 또는 두 개의 다른 스레드간에 이벤트 (이벤트 루프 사용)로 작동합니다. 신호 및 슬롯은에서 파생되는 클래스의 객체에서만 작동한다는 제한이 있습니다 QObject.

답변

나는 이런 식으로하고있다 :

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가 제안한 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')

산출:

첫 인사
인사말 오스카
안녕하세요 오스카

인사말 삭제
안녕하세요 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)