it-swarm.com.de

Ereignissystem in Python

Welches Ereignissystem für Python verwenden Sie? Ich kenne bereits pydispatcher , aber ich habe mich gefragt, was sonst noch zu finden ist oder häufig verwendet wird.

Ich bin nicht an Event-Managern interessiert, die Teil großer Frameworks sind. Ich würde lieber eine kleine Bare-Bones-Lösung verwenden, die ich problemlos erweitern kann.

160
Josip

Zusammenfassung der verschiedenen Ereignissysteme, die in den Antworten hier erwähnt werden:

Der grundlegendste Stil des Ereignissystems ist die "Tasche von Handler-Methoden", bei der es sich um eine einfache Implementierung des Observer-Pattern handelt. Grundsätzlich werden die Handler-Methoden (Callables) in einem Array gespeichert und jeweils aufgerufen, wenn das Ereignis ausgelöst wird.

  • zope.event zeigt die nackten Knochen, wie das funktioniert (siehe Lennarts Antwort ). Hinweis: Dieses Beispiel unterstützt nicht einmal Handlerargumente.
  • LongPokes 'callable list' Implementierung zeigt, dass ein solches Ereignissystem sehr minimalistisch implementiert werden kann, indem list subclassing wird.
  • spassigs EventHook (Michael Foords Event Pattern) ist eine unkomplizierte Implementierung.
  • Josip's Valued Lessons Event-Klasse ist im Grunde dasselbe, verwendet jedoch eine set anstelle einer list zum Speichern des Beutels und implementiert __call__, was beides sinnvolle Ergänzungen sind.
  • PyNotify hat ein ähnliches Konzept und bietet auch zusätzliche Konzepte von Variablen und Bedingungen ('variables geändertes Ereignis').
  • axel ist im Grunde ein Bag-of-Handler mit mehr Funktionen in Bezug auf Threading, Fehlerbehandlung, ...

Der Nachteil dieser Ereignissysteme besteht darin, dass Sie die Handler nur für das eigentliche Ereignisobjekt (oder die Liste der Handler) registrieren können. Zum Zeitpunkt der Registrierung muss das Ereignis bereits vorhanden sein.

Aus diesem Grund gibt es die zweite Art von Ereignissystemen: das Publish-Subscribe-Muster . Hier registrieren sich die Handler nicht für ein Ereignisobjekt (oder eine Handlerliste), sondern für einen zentralen Dispatcher. Auch die Melder sprechen nur mit dem Dispatcher. Worauf Sie achten oder was veröffentlicht werden soll, wird durch 'Signal' bestimmt, das nichts anderes als ein Name (String) ist.

  • blinker verfügt über einige nützliche Funktionen, wie z. B. das automatische Trennen der Verbindung und das Filtern nach Absender.
  • PyPubSub erscheint auf den ersten Blick recht unkompliziert.
  • PyDispatcher scheint Flexibilität in Bezug auf viele-zu-viele-Veröffentlichungen usw. zu betonen.
  • louie ist ein überarbeiteter PyDispatcher, der Plugin-Infrastruktur einschließlich Twisted- und PyQt-spezifischer Unterstützung bereitstellt. Es scheint nach Januar 2016 an Wartung zu verlieren.
  • Django.dispatch ist ein umgeschriebener PyDispatcher "mit einer eingeschränkteren Benutzeroberfläche, aber höherer Leistung".
  • Die Signale und Slots von Qt sind unter PyQt oder PySide verfügbar. Sie arbeiten als Callback, wenn sie im selben Thread verwendet werden, oder als Ereignisse (unter Verwendung einer Ereignisschleife) zwischen zwei verschiedenen Threads. Signale und Slots haben die Einschränkung, dass sie nur in Objekten von Klassen funktionieren, die von QObject abgeleitet sind.

Hinweis: threading.Event ist kein "Ereignissystem" im oben genannten Sinne. Es ist ein Thread-Synchronisierungssystem, bei dem ein Thread wartet, bis ein anderer Thread das Ereignisobjekt "signalisiert".

Hinweis: Oben noch nicht enthalten sind pypydispatcher , Python-dispatch und das 'Hook-System' von pluggy könnte ebenfalls von Interesse sein.

114
florisla

Ich habe es so gemacht:

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)

Wie bei allem, was ich bisher gesehen habe, gibt es dafür kein automatisch generiertes Pydoc und keine Signaturen, was wirklich scheiße ist.

Wir verwenden einen EventHook wie von Michael Foord in seinem Event Pattern vorgeschlagen :

Fügen Sie Ihrer Klasse einfach EventHooks hinzu mit:

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()

Wir fügen die Funktionalität hinzu, um alle Listener aus einem Objekt der Michaels-Klasse zu entfernen.

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
61
spassig

Ich benutze zope.event . Es ist das Nackteste, was man sich vorstellen kann. : -) .__ In der Tat ist hier der vollständige Quellcode:

subscribers = []

def notify(event):
    for subscriber in subscribers:
        subscriber(event)

Beachten Sie, dass Sie beispielsweise keine Nachrichten zwischen Prozessen senden können. Es ist kein Messaging-System, nur ein Ereignissystem, nicht mehr und nicht weniger.

16
Lennart Regebro

Ich habe dieses kleine Skript in Valued Lessons gefunden. Es scheint genau das richtige Verhältnis zwischen Einfachheit und Leistung zu haben, nach dem ich suche. Peter Thatcher ist der Autor des folgenden Codes (es wird keine Lizenzierung erwähnt).

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()
13
Josip

Sie können einen Blick auf pymitter ( pypi ) werfen. Es handelt sich um einen kleinen Ansatz mit einer einzelnen Datei (~ 250 loc)

Hier ist ein grundlegendes Beispiel:

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"
8
Dalailirium

Ich habe eine EventManager-Klasse erstellt (Code am Ende). Die Syntax lautet wie folgt:

#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()

Hier ist ein Beispiel:

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')

Ausgabe:

Anfangsgruß 
Grüße Oscar 
Hallo Oscar 

Nun entferne Grüße 
Hallo Oscar

EventManger-Code:

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)
6
Cristian Garcia

Hier ist ein minimales Design, das gut funktionieren sollte. Sie müssen einfach Observer in einer Klasse erben und anschließend observe(event_name, callback_fn) verwenden, um auf ein bestimmtes Ereignis zu warten. Wenn dieses bestimmte Ereignis an einer beliebigen Stelle im Code ausgelöst wird (z. B. Event('USB connected')), wird der entsprechende Rückruf ausgelöst.

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)

Beispiel:

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')
5
Pithikos

Ich habe eine Variation des minimalistischen Ansatzes von Longpoke vorgenommen, der auch die Signaturen für Anrufer und Anrufer gewährleistet:

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.__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()
5
Felk

Wenn ich in pyQt codiere, verwende ich QT-Sockets/Signale-Paradigma, das gleiche gilt für Django

Wenn ich asynchrone E/A-Vorgänge verwende, verwende ich das native Auswahlmodul

Wenn ich einen SAX-Python-Parser benutze, verwende ich die von SAX bereitgestellte Ereignis-API. So wie es aussieht, bin ich Opfer der zugrundeliegenden API :-)

Vielleicht sollten Sie sich fragen, was Sie vom Event-Framework Modul erwarten. Meine persönliche Präferenz ist die Verwendung des Socket/Signal-Paradigmas von QT. Mehr Infos dazu finden Sie/- hier

3
SashaN

Hier ist ein weiteres Modul zur Prüfung. Es scheint eine gangbare Wahl für anspruchsvollere Anwendungen zu sein.

Py-notify ist ein Python-Paket Bereitstellung von Tools zur Implementierung von Beobachterprogrammiermuster. Diese Zu den Tools gehören Signale, Bedingungen und Variablen.

Signale sind Listen von Handlern, die .__ sind. Wird aufgerufen, wenn das Signal gesendet wird . Die Bedingungen sind grundsätzlich boolesch Variablen gekoppelt mit einem Signal, dass wird ausgegeben, wenn der Zustandszustand Änderungen. Sie können mit .__ kombiniert werden. logische Standardoperatoren (nicht, und usw.) in zusammengesetzte Bedingungen . Im Gegensatz zu Bedingungen können Variablen __. jedes Python-Objekt, nicht nur boolesche Objekte, aber sie können nicht kombiniert werden.

2
Josip

Wenn Sie kompliziertere Dinge wie das Zusammenführen von Ereignissen oder das Wiederholen von Versuchen ausführen möchten, können Sie das Observable-Muster und eine ausgereifte Bibliothek verwenden, die dies implementiert. https://github.com/ReactiveX/RxPY . Observables sind in Javascript und Java sehr häufig und für einige asynchrone Aufgaben sehr praktisch. 

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())

AUSGABE:

Received Alpha
Received Beta
Received Gamma
Received Delta
Received Epsilon
Done!
0
David Dehghan

Sie können buslane module ausprobieren. 

Diese Bibliothek erleichtert die Implementierung eines nachrichtenbasierten Systems. Es unterstützt Befehle (Einzelhandler) und Ereignisse (0 oder mehrere Handler). Buslane verwendet Python-Typanmerkungen, um den Handler ordnungsgemäß zu registrieren.

Einfaches Beispiel:

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='[email protected]',
            password='secret',
        )


command_bus = CommandBus()
command_bus.register(handler=RegisterUserCommandHandler())
command_bus.execute(command=RegisterUserCommand(
    email='[email protected]',
    password='secret',
))

Um buslane zu installieren, benutze einfach pip:

$ pip install buslane
0
Konrad Hałas

Wenn Sie einen Eventbus benötigen, der über Prozess- oder Netzwerkgrenzen hinweg funktioniert, können Sie PyMQ versuchen. Derzeit werden Pub/Sub, Nachrichtenwarteschlangen und synchrones RPC unterstützt. Die Standardversion funktioniert auf einem Redis-Backend, daher benötigen Sie einen laufenden Redis-Server. Es gibt auch ein In-Memory-Backend zum Testen. Sie können auch Ihr eigenes Backend schreiben.

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')

So initialisieren Sie das System:

from pymq.provider.redis import RedisConfig

# starts a new thread with a Redis event loop
pymq.init(RedisConfig())

# main application control loop

pymq.shutdown()

Haftungsausschluss: Ich bin der Autor dieser Bibliothek

0
thrau

Vor einiger Zeit habe ich eine Bibliothek geschrieben, die für Sie nützlich sein könnte. Es ermöglicht Ihnen, lokale und globale Listener zu haben, sie auf verschiedene Arten zu registrieren, mit Ausführungspriorität zu arbeiten und so weiter.

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

Schauen Sie mal rein pyeventdispatcher

0
Daniel Ancuta