Python | Decoratoren richtig nutzen?
Guten Tag,
ich arbeite in Moment an ein kleines Projekt mit einer API für den Privatgebrauch.
Ich möchte mithilfe von Decoratoren ein Event registrieren.
Dieser Code ist nur ein Beispielcode und soll nur darstellen, was ich genau erreichen möchte:
main.py:
import time
class Bot:
_events: list = []
def __init__(self):
... # Übergabe der Instanz...
@classmethod
def event(cls, func):
... # Überprüfen, ob es sich um ein gültiges Event handelt
cls._events.append(func) # Registiert das Event, falls es gültig ist
return func
@classmethod
def event_handler(cls):
for event in cls._events:
if callable(event):
event()
def start(self) -> None:
while True:
time.sleep(1)
...
if __name__ == "__main__":
bot = Bot()
bot.start()
Alle Events, die es gibt:
class Events:
"""Events die Verfügbar sind"""
def on_check(self, chat):
...
def messages(self, message):
...
Die Nutzung:
class MyBot:
"""Das ist ein gültiges Event -> Funkionsname vom Event richtig, Parameter richtig
Kann also in die Registration aufgenommen werden"""
@bot.event
def on_check(self, chat):
...
"""Kein gültiges Event -> Sprich die Funktion event soll ein Fehler ausgeben"""
@bot.event
def on_name(self, name):
...
"""Hier fehlt der Parameter -> Sprich die Funktion event soll ein Fehler ausgeben"""
@bot.event
def messages(self):
...
Ich hoffe es ist durch den Kommentaren klar, was mein Ziel ist. Falls Unklarheiten bestehen, stehe ich gerne zur Verfügung.
Vielen dank für eure Hilfe!
Liebe Grüße
2 Antworten
Ich würde ein Singleton erstellen, welches alle Callbacks speichert.
class EventHandler:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(EventHandler, cls).__new__(cls)
cls._instance.callbacks = []
return cls._instance
def can_be_registered(self, callback):
if not callable(callback):
return False
name = getattr(callback, "__name__", "")
if not name in ["do_something"]:
return False
return True
def register(self, callback):
if self.can_be_registered(callback):
self.callbacks.append(callback)
else:
raise Exception("Callback can not be registered. Check its signature again.")
def unregister(callback):
self.callbacks.remove(callback)
def register_callback(callback):
EventHandler().register(callback)
Die Registrierung übernimmt die register_callback-Funktion.
@register_callback
def do_something():
# ...
Die obige Prüfung, ob der Callback registriert werden kann, ist noch primitiv. Für mehrere unterschiedliche Signaturen wäre es wohl besser, eine Klasse anzulegen, die eine Eventsignatur beschreibt. Das heißt, es gibt einen Namen und ein Array, welches die erlaubten Parametertypen beinhaltet.
import inspect
class EventType:
def __init__(self, name, parameters):
self.name = name
self.parameters = parameters
def callback_is_valid(self, callback):
if not callable(callback):
return False
name = getattr(callback, "__name__", "")
if not name == self.name:
return False
callback_signature = inspect.signature(callback)
parameters = callback_signature.parameters.values()
return self.signature_is_valid(parameters)
def signature_is_valid(self, parameters):
# ...
Die EventHandler-Klasse hätte folglich noch eine Liste von EventTyp-Objekten, welche in can_be_registered mit dem neu registrierten Callback verglichen werden.
Die Callback-Signatur kann mittels des inspect-Moduls erfragt werden. Du könntest nun prüfen, ob die Anzahl der gegebenen Parameter übereinstimmt oder Annotationen betrachten:
for parameter in parameters:
print(parameter.annotation.__name__)
Wenn es keine Annotation gibt, ist der Wert _empty.
Dort gibt es nur die Bedingung, dass die registrierte Funktion eine Coroutine sein muss. Sie wird unter ihrem Namen registriert und ihr Aufruf findet innerhalb eines try-except-Blocks statt.
Achso! Danke für die Klarstellung! :)
Kannst du mir eventuell noch einen Link zu der Zeile schicken, wo genau dieser try und except erfolgt? Denn ich habe das nichts finden können, deshalb dachte ich, dass alles in dem Register abgefragt und passiert.
Die dispatch-Methode ermittelt den Handler für ein Event. Wenn der gefunden wurde, wird er in die Warteschlange ausstehender Tasks aufgenommen. In _run_event wird der Handler innerhalb eines try-Blocks ausgeführt.
Kurz gesagt:
wie kann ich mithilfe von Decoratoren in Python ein Event richtig registrieren und auch überprüfen (vor dem registrieren), ob der Funktionsname zu dem eines Events stimmt und auch ob erforderliche Parameter angegeben sind?
So ähnlich wie bei der discord.py Bibliothek.
LG
Danke für deine Antwort.
Wie handhabt es denn discord.py? Ich habe in dem Code ausschließlich eine Abfrage gefunden, aber mehr auch nicht?…
https://github.com/Rapptz/discord.py/blob/425edd2e10b9be3d7799c0df0cd1d43a1a34654e/discord/client.py#L1961
Ich meine, wie will die Funktion dann wissen, ob es sich um ein gültiges Event handelt?