Eigenes Event System

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Donnerstag 30. Oktober 2008, 17:06

Hallo,

ich wollte für eines meiner Projekte eine Art "Event System" realisieren.

Die umgebende Struktur sieht so aus:

ConfigParser Dateien definieren für bestimmte Objekte die Art eines Events, also zb so:

Code: Alles auswählen

Handle_name: call::Event_name
Handle_name ist hier der Name eines Attribut des entsprechenden Objektes, das dahinter ist ein von mir selbst zurechtgeschnippelter "Statement" Aufruf. davon gibt es zwei, einmal "get" und einmal "call", die beide den Name eines Events entgegennehmen. "get" bedeutet, das der Rückgabewert des Events an "object.<handle_name>" gebunden werden soll. "call" bedeutet, das das fertig bearbeitete Event selbst an "object.<handle_name>" gebunden werden soll.

Die Events selber funktionieren bisher so:

Ich habe ein Plugin System gebaut, das aus einer zentralen CSV Datei die einzelnen Pfade der Events ausliest und intern dann an den ebenfalls in der CSV Datei definierten Namen bindet.

Die Events selbst sind py Dateien, werden also dann als plugin importiert. Jedes Event hat eine zentrale "make" Funktion, die zwei Parameter entgegennimmt: Das zentrale HandlerObjekt (zu dem ich gleich komme) und seinen Aufrufer.

Soweit, so gut, nun war der Gedanke, das so weiterzustricken:

Ich baue eine Art zentrales "Handler" Objekt, das eine API für die Events bildet, also festlegt, welche Methoden die Events benutzen können. Das Objekt selbst verwaltet die einzelnen Events und gibt ihnen auf Anfrage dann die Informationen, die sie haben wollen.

Jedes Event wird dann intern durch ein "Event-Objekt" dargestellt, das von threading.Thread abgeleitet ist, und hat einzig die Berechtigung, mithilfe des Handlers Dinge zu verändern oder abzufragen usw. Der Handler verwendet dabei bei jedem Methoden-Aufruf ein einziges threading.Lock Objekt, damit das ganze threadsafe ist.

Der Hintergrund, warum es Threads sein müssen, liegt in der "call" Methode. Es wird notwendig sein, Events zu definieren, die, wenn aufgerufen, in eine eigene Endlosschleife übergehen und bei bestimmten Aktivitäten bestimmte Dinge ändern. Ist das nicht der Fall, wird zwar unnötigerweise ein neuer Thread gestartet, der daraufhin gleich wieder kollabiert, aber um das ganze nicht mit zig Ausnahmen zu versehen, wird das wohl die beste Möglichkeit sein.

Das "Handler Objekt" wollte ich dann entsprechend durch zig property's (bzw get/set Methoden) realisieren, und eben Verwaltung für Events, wobei ich da noch keine genaue Vorstellung habe.

Sinn der Events ist es, die einzelnen Objekte dynamisch Dinge ausführen zu lassen. Da die Objekte hierarchisch organisiert sind, hat ein "höheres" Objekt durch entsprechende Events dann die Möglichkeit, die Erzeugung/Vernichtung "niederer" Objekte zu steuern, eben durch Events, je nach Event in Abhängigkeit bestimmter Gegebenheiten. Events selber und deren Aufrufe werden durch eine zentrale AI des Objektes gesteuert. So ist es zb auch möglich, eine AI durch die andere auszutauschen, die alle Events dann reinitialisiert und ihrer Handlungsart entsprechend steuert. Das beinhaltet natürlich, das jedes Objekt eine AI hat, was irgendwann bei den Objekten, die keine eigenen "Eltern Objekte" mehr haben, zu einer "GodAI" führt, die das "big picture" kontrolliert.

Hoffe, ich habe nichts vergessen. Nun die Frage: Haltet ihr das für eine gute Idee? :D
Qubit
User
Beiträge: 75
Registriert: Dienstag 7. Oktober 2008, 09:07

Donnerstag 30. Oktober 2008, 21:05

str1442 hat geschrieben:ich wollte für eines meiner Projekte eine Art "Event System" realisieren.
Eventgetriebene Applikationen sind ein interessantes Thema ;-)
Ich habe da ein recht einfaches Event-Modell:

Eine zentrale Eventmanager Instanz läuft als Thread und jede Klasse, die das Interface implementiert, kann "Slots" beim "Listener" des Eventmanagers anfordern (mit Metadaten, Priorisierung, etc.). Diese Klassen sind selbst verantwortlich ein Event zu triggern (resp. als Mittler externer Events). Alle Klassen, die an einem Event "interessiert" sind, können sich über das Interface beim Event-Handler des Eventmanagers mit einem Callback registrieren. Beim Notifying wird nun durch eine Klasse ein Event am Listener ausgelöst und über den Handler alle registrierten Callbacks des Slots (nach Priorisierung) aufgerufen und Metadaten übergeben. Diese Implementierung ist völlig frei von Applikationslogik, d.h. das Eventsystem selbst enthält keine Methoden, die Abläufe in der Business Logik ändern, diese bleibt in den Logikklassen. Prozesse die aufgrund der Events nun angestossen werden sollen, müssen daher schon in der Applikationslogik modelliert sein. Insofern bleibt der Applikationsstatus deterministisch, auch wenn die Prozesse es nicht sein müssen.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Samstag 1. November 2008, 00:30

Das Problem, das ich dabei sehe, ist das einerseits Eventlogik und andererseits Programmlogik direkt implementiert werden müssen.

Du hast zwar eine Trennung, aber du schreibst ja schon selber, das jede Klasse, die teilnehmen will A. Ein mehr oder weniger statisches Interface implementieren muss, und b die Events sich auf das Vorhandensein einer zb bestimmten Methode verlassen müssen, die mit der Logik zu tun hat. Damit hat man dann zig verschiedene Events und gleichzeitig die Logik noch festgemauert. Oder verstehe ich dich da falsch?

Ich wollte mithilfe meiner "Event-Plugins" die Programmlogik von aussen "injizieren". Ich habe im Grunde eine Engine, die über diverse ConfigParser und CSV Dateien sich ihre Daten holt, und deren einziger Grundbaustein eine Reihe von Objekten sind, die diverse Attribute, die ihre Eigenschaften definieren, aberkeine Methoden sind, besitzen. So ist jedes Objekt an sich erstmal ein "Container".

Die "Methodenlogik" wollte ich nun durch Plugins realisieren: Jedes Plugin (oder wie ichs hier genannt habe, Event) besitzt demnach eine Logik, die dann je nach Gebrauch und je nach Festlegung in der Konfiguration zugewiesen wird, und sich immer gleich verhält. Das kann die Zuweisung eines Wertes zu einem bestimmten Attribut sein (die "get" Methode) oder die Zuweisung der Logik zu einem Attribut (die "call" Methode).

Der einzige andere Platz, an dem nun direkt auf diese gesetzen Attribute zugreifen kann, ist über die AI Klassen, die ja direkt ihre Objekte steuern müssen. Das halte ich aber auch für OK, denn ist eine solche "Plugin-Methode" nicht implementiert, passiert eben nichts bei Aufruf durch die AI Klasse (das seinerseits natürlich wieder austauschbar ist, um anderes AI Verhalten zu implementieren (Aggresive AI vs Defensive Ai usw)).

Problem an der Geschichte sind die Events (Gut, die Plugins sind mit Events nicht eindeutig, mein Fehler). Ich muss natürlich über solche "Plugin-Methoden" andere Objekte beeinflussen können, und so wollte ich jede Plugin Methode in einen eigenen Thread stecken, und über ein zentrales Handler Objekt die einzelnen Interaktionen steuern (was ja EventHandling doch wieder nahe kommt).

Und hier weiß ich eben nicht, ob das nicht doch ziemlicher Quatsch ist. Zumal grade bei solchen Sachen wie "Mache nach Abhängigkeit von Wert xy alle 30 Sekunden Aktion z" ich nicht sicher bin, ob das dann nicht extrem umständlich zu kontrollieren wird.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 1. November 2008, 12:48

Ich reformuliere: Du hast Objekte mit Attributen. Ändert sich ein Attribut, soll eine Funktion aufgerufen werden ("call"). Das Ergebnis dieser Funktion kann in ein anderes Attribut (eines möglicherweise anderen Objekts) geschrieben werden ("get").

Kann man so machen.

Das klassische Muster ist ein Publish-Subscribe-Verfahren. Irgend etwas publiziert Ereignisse, auf die andere achten. Will man es explizit machen, könnte das so aussehen:

Code: Alles auswählen

class Source(object):
    def __init__(self):
        self._subscribers = {}
    
    def subscribe(self, subscriber, aspect):
        self._subscribers.setdefault(aspect, []).append(subscriber)
    
    def publish_aspect_change(self, aspect, value):
        if aspect in self._subscribers:
            for subscriber in self._subscribers[aspect]:
                subscriber(self, aspect, value)

class MyModel(Source):
    def __setattr__(self, name, new_value):
        old_value = self.__dict__.get(name)
        if old_value != new_value:
            self.__dict__[name] = new_value
            self.publish_aspect_change(name, new_value)
    
class Subscriber(object):
    def __init__(self, func, target=None):
        self.func, self.target = func, target
    
    def __call__(self, source, aspect, new_value):
        result = self.func(source, aspect, new_value)
        if self.target:
            self.target(result)

def handler(source, aspect, value):
    return value * 2

def attrsetter(obj, name):
    def inner(value): setattr(obj, name, value)
    return inner

m1 = MyModel()
m2 = MyModel()
m1.subscribe(Subscriber(handler, attrsetter(m2, "attr")), "attr")
m1.attr = 2
print m2.attr
Die Liste der Abonnenten sollte "weak" gehalten werden, damit diese verschwinden können, ohne dass sie sich explizit abmelden müssen oder wenig von dem Publizierer festgehalten werden.

Einen zentralen Dispatcher könnte man auch bauen, finde ich aber umständlicher und weniger objektorientiert als wenn die Objekte selbst Ereignisse erzeugen und sich für andere Ereignisse interessieren.

Wenn ich lese, dass du mit Threads arbeiten willst, bin ich mir nicht mehr sicher, ob du wirklich Events haben willst oder nicht doch Werte pollen willst und auf diese Weise Änerungen über die Zeit finden willst. Das bewährt sich in der Regel nicht.

Ein "Event-System on Steroids" findest du übrigens unter dem Stichwort "cells" - das definiert ein Constraint-System ähnlich wie eine Tabellenkalkulation. Kann auch ganz nett sein. Ein Port für Python ist Trellis

Stefan
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Samstag 1. November 2008, 13:57

sma hat geschrieben:Ein "Event-System on Steroids" findest du übrigens unter dem Stichwort "cells" - das definiert ein Constraint-System ähnlich wie eine Tabellenkalkulation. Kann auch ganz nett sein. Ein Port für Python ist Trellis
Ich finde Trellis etwas arg kompliziert (wie in: keine Ahnung wie ich das irgendwie sinnvoll mit dem Rest meiner Programme verbinde), vielleicht ist PyCells eine bessere weil simplere Alternative. :?:
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Samstag 1. November 2008, 15:29

Ich reformuliere: Du hast Objekte mit Attributen. Ändert sich ein Attribut, soll eine Funktion aufgerufen werden ("call"). Das Ergebnis dieser Funktion kann in ein anderes Attribut (eines möglicherweise anderen Objekts) geschrieben werden ("get").
Nicht ganz. Eher hat jede Funktion "dummy Attribute", die nichts machen (also entweder None oder lambda self: None, wenn es eine Methode werden soll). Dann will ich in einer ConfigParser Datei für jedes Attribut ein Modul definieren. Dieses Modul wird dann aufgerufen (wie genau ist dann intern geregelt, also zb main funktion aufrufen). Bei "get" wird der Rückgabewert der Funktion an das Attribut gebunden, bei "call" soll die Funktion selbst an das Attribut gebunden werden.

Ich will also eine Basisklasse per ConfigDatei in gewisser Weise mit der "Hauptfunktion" eines Plugins monkey patchen (Hey, mit dem richtigen Vokabular klappts :)).

Der Grund, warum ich Threads verwenden will: Dabei kann es auch sein, das die einzelnen Module Funktionen definieren, die endlos lange laufen nach Aufruf, und ihre Api (das Handler Objekt) nach Informationen befragen, und entsprechend Dinge ändern.

Da also praktisch die gesamte Applikationslogik in diese Plugins ausgelagert wird, könnte ich über das Handler Objekt Events gleich mit realisieren. Hoffe, jetzt habe ich für Klarheit gesorgt.


Allerdings glaube ich inzwischen, das das eine ziemlich blöde Idee ist. Ich will stattdessen versuchen, die Basisobjekte selbst als plugins auszulagern und dann doch dort alle Applikationslogik zu implementieren. Events werde ich trotzdem brauchen, weswegen ich sie auch implementieren werde. Endlosfunktionen, die bei bestimmten Gegebenheiten bestimmte Dinge tun, kann ich so zwar nicht so einfach implementieren, aber ich halte mich da mal an YAGNI. Ich glaube, was ich hier als Endlosfunktionen beschreibe, meinst du mit "werte über die Zeit pollen".

Dein Code hat aber schonmal weitergeholfen in Bezug auf die Events. Ich muss das alles mal in Ruhe durchprobieren. Danke!

Trellis:
Hört sich interessant an. Ja, das durch Events auch Race Conditions entstehen können hört sich plausibel an. Etwas ähnliches wie Leonidas hab ich mir allerdings auch gedacht, als ich den Beispielcode gesehen hab. Sieht etwas schwer zu integrieren aus.
Antworten