Python | Decoratoren richtig nutzen?

2 Antworten

Vom Fragesteller als hilfreich ausgezeichnet

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.

GettingHeart 
Fragesteller
 23.03.2024, 21:44

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?

0
regex9  24.03.2024, 17:37
@GettingHeart

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.

1
GettingHeart 
Fragesteller
 24.03.2024, 17:44
@regex9

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.

0
regex9  24.03.2024, 18:05
@GettingHeart

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.

1

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