Zum Inhalt springen
Vorschau auf das nächste Semester, noch nicht veröffentlicht.
Zur aktuellen Seite

Mastodon, Teil 2: Toots filtern

Aufgabe

So funktioniert ein Trigger

  1. Prüfen: Ein Trigger ist ein Objekt mit der Methode evaluate(self, toot). Sie bekommt ein Toot-Objekt und gibt True zurück, wenn der Toot das Kriterium erfüllt – man sagt: Der Trigger löst aus. Sonst gibt sie False zurück.
  2. Erben: Alle Trigger erben von der Superklasse Trigger. Deren evaluate löst nur einen Fehler (NotImplementedError) aus. Jede Unterklasse, die Sie auf Toots anwenden, muss evaluate also selbst überschreiben.
  3. Einfache Trigger prüfen eine Eigenschaft des Toots: Medienanhänge, eine Phrase im Text oder die Veröffentlichungszeit. Was genau sie prüfen, etwa welche Phrase, bekommen sie beim Erzeugen übergeben.
  4. Kombinierte Trigger (NotTrigger, AndTrigger, OrTrigger) bekommen beim Erzeugen andere Trigger übergeben. Sie rufen deren evaluate auf und verknüpfen die Ergebnisse mit not, and bzw. or.
  5. Filtern: filter_toots behält von einer Liste von Toots nur die, bei denen jeder Trigger aus einer Liste von Triggern auslöst.

Beispiel mit zwei Toots und dem kombinierten Trigger AndTrigger(PhraseTrigger("python"), NotTrigger(ImageMediaTrigger())) – „erwähnt Python und hat kein Bild“:

Toot AToot B
contentHeute lerne ich Python!Mein Python-Setup, siehe Foto.
mediakeineein Bild ("type" ist "image")
pubdate10.1.2026, 12:00 Uhr UTC10.1.2026, 14:00 Uhr UTC
PhraseTrigger("python")TrueTrue
ImageMediaTrigger()FalseTrue
NotTrigger(ImageMediaTrigger())TrueFalse
AndTrigger(…)TrueFalse

filter_toots mit beiden Toots und diesem Trigger gibt also nur Toot A zurück.

Zeit-Trigger rechnen mit Zeitzonen: BeforeTrigger("2026-01-10 08:00:00") meint 8 Uhr in der voreingestellten Zeitzone EST (Ostküste der USA, UTC−5), also 13:00 Uhr UTC. Er löst bei Toot A aus, bei Toot B nicht.

Spezifikation

Im Gerüst steht über jeder Klasse, von welcher Klasse sie erbt (etwa # MediaTrigger is a subclass of Trigger). Tragen Sie diese Superklasse in die Klassenzeile ein und ergänzen Sie die Parameter der Methoden.

Schritt 1: Medien-Trigger

  • MediaTrigger erbt von Trigger. evaluate gibt True zurück, wenn der Toot Medienanhänge hat, also toot.media nicht leer ist.
  • ImageMediaTrigger erbt von MediaTrigger. evaluate gibt True zurück, wenn der Toot Medienanhänge hat und mindestens einer davon ein Bild ist ("type" ist "image"). Nutzen Sie für die erste Bedingung das evaluate der Superklasse.
  • VideoMediaTrigger erbt von MediaTrigger und funktioniert genauso für Videos ("type" ist "video").

Mastodon kennt noch weitere Medientypen wie GIFs oder Audio; dafür brauchen Sie keine eigenen Trigger.

Schritt 2: PhraseTrigger

PhraseTrigger erbt von Trigger und löst aus, wenn der Text eines Toots eine bestimmte Phrase (ein Wort oder mehrere) enthält.

  • __init__(self, phrase) speichert die Phrase als Attribut.
  • evaluate wandelt den Text (toot.content) und die Phrase in Kleinbuchstaben um, entfernt aus dem Text die Satzzeichen ., ,, ! und ? (ersetzt sie durch den leeren String) und gibt True zurück, wenn die Phrase dann im Text enthalten ist.

So löst PhraseTrigger("Python") bei Heute lerne ich Python! aus, PhraseTrigger("Java") nicht.

Schritt 3: Zeit-Trigger

  • TimeTrigger erbt von Trigger. __init__(self, ptime, timezone="EST") bekommt die Auslösezeit ptime als String im Format "YYYY-MM-DD HH:MM:SS" und optional eine Zeitzone; ohne Angabe gilt "EST". Wandeln Sie ptime in ein datetime-Objekt um, versehen Sie es mit der Bibliothek pytz mit dieser Zeitzone und speichern Sie das Ergebnis als Attribut. Die Uhrzeit bleibt dabei unverändert: "2026-01-10 08:00:00" bedeutet 8 Uhr EST. TimeTrigger ist eine abstrakte Klasse – sie dient nur als Superklasse und braucht kein evaluate.
  • BeforeTrigger erbt von TimeTrigger und damit auch dessen __init__. evaluate gibt True zurück, wenn der Toot (toot.pubdate) strikt vor der Auslösezeit veröffentlicht wurde.
  • AfterTrigger erbt ebenso von TimeTrigger und gibt True zurück, wenn der Toot strikt nach der Auslösezeit veröffentlicht wurde.

Schritt 4: Trigger kombinieren

Alle drei erben von Trigger, speichern die übergebenen Trigger als Attribute und werten sie in evaluate für den jeweiligen Toot aus:

  • NotTrigger(trigger) kehrt das Ergebnis um: True, wenn trigger nicht auslöst, sonst False.
  • AndTrigger(trigger1, trigger2) gibt nur dann True zurück, wenn beide Trigger auslösen.
  • OrTrigger(trigger1, trigger2) gibt True zurück, wenn mindestens einer der beiden auslöst.

Schritt 5: filter_toots

filter_toots(toots, trigger_list) bekommt eine Liste von Toots und eine Liste von Triggern. Sie gibt eine Liste der Toots zurück, bei denen jeder Trigger aus trigger_list auslöst (Aufruf von evaluate). Bilden Sie diese Liste mit einer List Comprehension.

Schritt 6: Toots filtern und ausgeben

Im Abschnitt if __name__ == '__main__':

  1. laden Sie Toots mit load,
  2. erzeugen Sie einige Trigger und Kombinationen davon,
  3. fassen Sie die Trigger, die gelten sollen, in einer Liste zusammen,
  4. rufen Sie filter_toots mit den Toots und dieser Liste auf,
  5. geben Sie die gefilterten Toots mit print aus (dabei wird die __str__-Methode Ihrer Klasse Toot verwendet).

Achten Sie darauf, dass sich Ihre Trigger nicht gegenseitig ausschließen. Ein AndTrigger aus BeforeTrigger("2024-01-01 00:00:00") und AfterTrigger("2024-01-01 00:00:00") etwa lässt keinen Toot durch.

Hilfestellung

Klicken Sie auf die folgenden Tipps, um einige Ratschläge zu erhalten. Versuchen Sie aber zunächst, selbst so weit wie möglich zu kommen.

MediaTrigger und ImageMediaTrigger

Prüfen Sie zuerst selbst:

  • Erbt MediaTrigger von Trigger und ImageMediaTrigger von MediaTrigger?
  • Hat jede der beiden Klassen eine Methode evaluate(self, toot)?
  • Prüft evaluate, ob toot.media eine leere Liste ist?

Wenn das nicht weiterhilft: MediaTrigger könnte so aussehen.

class MediaTrigger(Trigger):
    def evaluate(self, toot):
        return bool(toot.media)

Bei ImageMediaTrigger ruft super().evaluate(toot) das evaluate von MediaTrigger auf. any(…) ist True, wenn mindestens ein Anhang in toot.media ein Bild ist:

class ImageMediaTrigger(MediaTrigger):
    def evaluate(self, toot):
        if not super().evaluate(toot):
            return False
        return any(media['type'] == 'image' for media in toot.media)
Auf den Typ eines Medienanhangs zugreifen

Lassen Sie sich toot.media eines Toots mit Bild einmal mit print ausgeben. Es ist eine Liste; jeder Anhang darin ist ein Dictionary. Den Typ eines einzelnen Anhangs anhang erhalten Sie mit anhang['type'].

Standardwert für einen Parameter

Ein Parameter mit Standardwert wird beim Aufruf nur angegeben, wenn man einen anderen Wert will:

def my_function(parameter="bar"):
    print(f"foo, {parameter}")

my_function("my value")  # gibt foo, my value aus
my_function()            # gibt foo, bar aus
Zeit und Zeitzone mit datetime und pytz
  • datetime.strptime(ptime, "%Y-%m-%d %H:%M:%S") macht aus dem String ein datetime-Objekt, zunächst ohne Zeitzone.
  • pytz.timezone(timezone) liefert ein Zeitzonen-Objekt. Dessen Methode localize versieht ein datetime ohne Zeitzone mit dieser Zeitzone und lässt die Uhrzeit, wie sie ist.
  • Verwenden Sie nicht astimezone: Diese Methode rechnet die Uhrzeit in eine andere Zeitzone um, statt ihr eine Zeitzone zuzuordnen – aus 8 Uhr würde eine andere Uhrzeit.
  • toot.pubdate hat bereits eine Zeitzone (UTC). Zwei datetime-Objekte mit Zeitzone können Sie direkt mit < und > vergleichen.
Mein Filter lässt keinen Toot durch
  • load holt die neuesten Toots. Liegt Ihr Zeitfenster in der Vergangenheit, passt keiner. Lassen Sie sich toot.pubdate der geladenen Toots ausgeben und wählen Sie die Zeiten passend.
  • Testen Sie jeden Trigger zunächst einzeln, bevor Sie Trigger kombinieren.

Testen

Führen Sie Ihr Programm aus:

python mastodon_oop.py

Ändern Sie dann die Trigger und ihre Kombinationen, bis Sie jeden Trigger mindestens einmal verwendet haben, und prüfen Sie jeweils, ob genau die passenden Toots ausgegeben werden.

Welche Toots Mastodon liefert, ändert sich ständig. Sie können die Toots aus dem Beispiel oben deshalb auch selbst bauen und prüfen, ob Ihre Trigger die Werte aus der Tabelle liefern:

a = Toot("Heute lerne ich Python!", "alice", ["python"],
         datetime(2026, 1, 10, 12, 0, tzinfo=pytz.utc), [])
b = Toot("Mein Python-Setup, siehe Foto.", "bob", ["python"],
         datetime(2026, 1, 10, 14, 0, tzinfo=pytz.utc), [{"type": "image"}])
trigger = AndTrigger(PhraseTrigger("python"), NotTrigger(ImageMediaTrigger()))
print(trigger.evaluate(a), trigger.evaluate(b))  # True False
print(BeforeTrigger("2026-01-10 08:00:00").evaluate(a))  # True

Korrektheit

Führen Sie in Ihrem Terminal den folgenden Befehl aus, um die Korrektheit Ihrer Arbeit zu überprüfen:

check50 -l inf-zone/exercises/2026/mastodon/filter

Style

Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50Prüfprogramm von CS50, das zeigt, wo Ihr Code von den Formatierungsregeln abweicht. In Grün zeigt es, was Sie ergänzen sollten, in Rot, was wegfallen soll. Glossar → zu analysieren:

style50 mastodon_oop.py

Wo sind Sie gut vorangekommen, wo hätten Sie sich mehr Hilfe gewünscht? Schreiben Sie es uns im Forum oder sagen Sie es in den Tutorien – das hilft uns, die Aufgabe zu verbessern.

Wie Teil 1 stammt diese Aufgabe von der FAU Erlangen-Nürnberg und steht unter der Lizenz CC BY-NC-SA 4.0 (Nachweis in Teil 1).

Abgeben

Geben Sie im Ordner mastodon_oop ab:

inf upload mastodon/filter

Danach sehen Sie, welche Tests Ihr Programm besteht und ob die Übung für die Bonuspunkte zählt. Wie Sie inf installieren und sich anmelden, steht unter Abgeben mit inf.

Zum Weiterlesen: Vererbung und Polymorphie

Für die Lösung der Aufgabe nicht nötig.

Beim Filtern haben Sie Polymorphie genutzt: filter_toots ruft für jeden Trigger evaluate auf, ohne zu wissen, um welche Art von Trigger es sich handelt. Welches evaluate ausgeführt wird, hängt von der Klasse des Objekts ab. Deshalb können Sie neue Trigger ergänzen, etwa einen für Hashtags, ohne filter_toots zu ändern.

Trigger und TimeTrigger sind abstrakte Klassen: Sie legen fest, was alle Unterklassen gemeinsam haben, werden aber selbst nicht auf Toots angewendet. Python bietet dafür auch ein eigenes Modul, abc (Abstract Base Classes). Damit meldet Python schon beim Erzeugen eines Objekts einen Fehler, wenn eine Unterklasse eine vorgeschriebene Methode nicht implementiert – nicht erst beim Aufruf.

Diese Seite als Markdown: ansehen herunterladen