Mastodon, Teil 2: Toots filtern
In Teil 1 haben Sie Toots geladen und als Toot-Objekte abgebildet. In diesem Teil filtern Sie sie: Sie schreiben Trigger-Klassen, die prüfen, ob ein Toot bestimmte Kriterien erfüllt, und wenden sie auf die geladenen Toots an. Sie arbeiten in derselben Datei mastodon_oop.py weiter.
Filtermechanismen – Trigger
Trigger sind Regeln, die bestimmen, ob ein Beitrag die festgelegten Kriterien erfüllt. Sie können sich auf den Inhalt der Beiträge beziehen, z.B. auf bestimmte Phrasen, Medientypen oder der Zeitpunkt, zu dem die Beiträge veröffentlicht wurden. Trigger können einzeln verwendet oder mithilfe von zusammengefassten Triggern kombiniert werden, um komplexere Filterregeln zu erstellen.
Ihre Aufgabe ist es, Trigger zu erstellen, die auf die in der load-Funktion geladenen Toots angewendet werden.
Nachdem Sie alle Trigger-Klassen erstellt haben, schreiben Sie eine Funktion, die eine Stichprobe von Triggern und Toots durchgeht und überprüft, ob ein bestimmter Beitrag alle angegebenen Trigger-Kriterien erfüllt.
In den folgenden Teilaufgaben werden Sie Stück für Stück ans Ziel geleitet.
Trigger Superklasse
Hierbei handelt es sich um die Trigger-Klasse. Im gegeben Code ist diese Klasse bereits fertig implementiert. Sie müssen daran nichts mehr ändern.
Es handelt sich dabei um eine Klasse, die als Superklasse für alle Trigger dient. Sie enthält die Methode evaluate, die verwendet wird, um zu beurteilen, ob ein Filter für einen bestimmten Beitrag in Mastodon erfüllt ist. Beachten Sie, dass die evaluate-Methode ein Toot-Objekt übergeben bekommt. Alle Trigger-Klassen sollen von dieser Klasse erben.
Eine Unterklasse verwendet die Methoden der Superklasse, wenn sie diese nicht selbst überschreibt. Wenn Sie die Methode evaluate in einer Unterklasse nicht überschreiben, wird die Methode der Superklasse (Trigger) aufgerufen. Diese löst jedoch einen Fehler (NotImplementedError) aus, weil sie nicht implementiert ist.
Media-Trigger
In dieser Teilaufgabe werden wir die Trigger-Klasse verwenden. Sie sollen hier die MediaTrigger-Klasse implementieren. Diese ist eine spezielle Art von Trigger, der ausgelöst wird, wenn ein Toot Medienanhänge wie Bilder, Videos, GIFs oder Audiodateien enthält.
Um diesen Trigger zu implementieren, greifen Sie auf das media-Attribut des Toot-Objekts zu, das die Medienanhänge speichert. Überprüfen Sie, ob dieses media-Attribut nicht leer ist. Falls es Medienanhänge enthält, geben Sie True zurück. Wenn keine Medien vorhanden sind, geben Sie False zurück. Denken Sie daran, dass diese Klasse von der Trigger-Klasse erbt.
Sie sind sich unsicher, ob Sie diese Klasse richtig implementiert haben?
Folgende Fragen, können Ihnen bei der Implementierung helfen:
- Haben Sie die
MediaTrigger-Klasse so implementiert, dass sie von derTrigger-Klasse erbt? - Hat Ihre
MediaTrigger-Klasse eineevaluate-Methode? - Prüfen Sie in der
evaluate-Methode, ob ein Toot-Objekt Medienanhänge hat? Das heißt, prüfen Sie, obmediaeine leere Liste ist und geben dementsprechendTrueoderFalsezurück?
Können Sie alle Fragen mit “Ja” beantworten?
Sollten diese Fragen nicht weiter helfen, können Sie sich selbstverständlich die unten stehenden Tipps anschauen.
Tipps
- Die
MediaTrigger-Klasse sollte von derTrigger-Klasse erben.class MediaTrigger(Trigger): ... - Sie sollte eine
evaluate-Methode beinhalten.def evaluate(self, toot): ... - Es muss sichergestellt werden, ob ein Toot-Objekt überhaupt Medienanhänge enthält. Dementsprechend muss geprüft werden, ob
mediaeine leere Liste ist. Ist dies der Fall, so beinhaltet das entsprechende Toot-Objekt keine Medienanhänge. Es soll alsoFalsezurückgegeben werden – andernfallsTrue:def evaluate(self, toot): return bool(toot.media)
Image-Trigger
Nun wollen wir in einer weiteren Klasse von der MediaTrigger-Klasse erben. Dafür sollen Sie die ImageMediaTrigger-Klasse implementieren.
Der ImageMediaTrigger wird ausgelöst, wenn ein Beitrag ein oder mehrere Bildanhänge enthält. Das heißt es muss ein nicht leeres media-Attribut geben und zusätzlich muss dieses ein Bild sein. Somit sollte Ihre Klasse von MediaTrigger erben.
Um diesen MediaTrigger zu implementieren, greifen Sie auf das media-Attribut des Toot-Objekts zu, das die Medienanhänge speichert. Überprüfen Sie, ob das media-Attribut nicht leer ist und mindestens einen Bildanhang enthält.
Was steckt nun hinter dem media-Attribut? Wie können Sie auf den Typ eines Medienanhangs zugreifen, um zu prüfen, ob es ein Bildanhang ist? Lassen Sie sich hierfür das media-Attribut einmal auf der Konsole ausgeben.
Wenn mindestens ein Bildanhang enthalten ist, geben Sie True zurück, was anzeigt, dass der Trigger auslöst. Andernfalls geben Sie False zurück. Wenn es sich um einen Bildanhang handelt, so ist type == 'image'. Beachten Sie, dass diese Klasse von der MediaTrigger-Klasse erbt.
Sie sind sich unsicher, ob Sie diese Klasse richtig implementiert haben?
Folgende Fragen, können Ihnen bei der Implementierung helfen:
- Haben Sie die
ImageMediaTrigger-Klasse so implementiert, dass sie von derMediaTrigger-Klasse erbt? - Hat Ihre
ImageMediaTrigger-Klasse eineevaluate-Methode? - Ist Ihnen klar, dass Sie mittels
media[type]auf dentypeeines Medienanhangs zugreifen können? - Stellen Sie sicher, dass Sie beim Aufruf von
evaluateauch prüfen, ob das Toot-Objekt Medienanhänge enthält?
Sollten diese Fragen nicht weiter helfen, können Sie sich selbstverständlich die unten stehenden Tipps anschauen.
Tipps
- Die
ImageMediaTrigger-Klasse sollte von derMediaTrigger-Klasse erben.class ImageMediaTrigger(MediaTrigger): ... - Sie sollte eine
evaluate-Methode beinhalten.def evaluate(self, toot): ... - Es muss sichergestellt werden, ob ein Toot-Objekt überhaupt Medienanhänge beinhaltet. Dabei können wir es uns zu nutze machen, dass diese Klasse von der
MediaTrigger-Klasse erbt. Dementsprechend könnte dieevaluate-Methode wie folgt aussehen:def evaluate(self, toot): if not super().evaluate(toot): return False return any(media['type'] == 'image' for media in toot.media) - Mit
return any(media['type'] == 'image' for media in toot.media)kann man nun prüfen, ob ein Medienanhang in der Listetoot.mediaein Bildanhang (image) ist.
Video-Trigger
Die VideoMediaTrigger-Klasse ist sehr ähnlich zur ImageMediaTrigger-Klasse, die Sie soeben implementiert haben.
Der VideoMediaTrigger wird ausgelöst, wenn ein Beitrag ein oder mehrere Videoanhänge enthält.
Um diesen MediaTrigger zu implementieren, greifen Sie auf das media-Attribut des Beitragobjekts zu, das die Medienanhänge speichert. Überprüfen Sie, ob das media-Attribut nicht leer ist und mindestens einen Videoanhang enthält.
Wenn dies der Fall ist, geben Sie True zurück, andernfalls geben Sie False zurück. Wenn es sich um einen Videoanhang handelt, so ist type == 'video'. Beachten Sie, dass diese Klasse von der MediaTrigger-Klasse erbt.
Trigger-Klassen mehr, sondern widmen uns noch einigen anderen Triggern.Phrase-Trigger
Um nach konkreten Phrasen (Sätzen/Wörtern) im Textinhalt eines Toots suchen zu können, sollen Sie nun eine PhraseTrigger-Klasse implementieren.
Der PhraseTrigger ist ein Trigger, der ausgelöst wird, wenn ein Toot eine bestimmte Phrase in seinem Textinhalt enthält.
Um diesen Trigger zu implementieren, übergeben Sie beim Erstellen einer Instanz von PhraseTrigger die gewünschte Phrase als Argument und speichern Sie sie als Attribut des Triggers.
Das heißt, im Gegensatz zu den Triggern, die Sie bis jetzt implementiert haben, benötigt dieser Trigger eine konkrete __init__-Methode, die ein Argument entgegen nehmen kann.
In der evaluate-Methode greifen Sie auf das content-Attribut des Toot-Objekts zu, um den Textinhalt des Toots zu erhalten.
Konvertieren Sie sowohl den Textinhalt als auch die Phrase in Kleinbuchstaben, um den Vergleich unabhängig von der Groß-/Kleinschreibung durchzuführen.
Entfernen Sie dann alle Satzzeichen (., ,, !, ?) aus dem Textinhalt, indem Sie sie durch leere Zeichen ersetzen. Überprüfen Sie abschließend, ob die Phrase im Textinhalt enthalten ist.
Wenn dies der Fall ist, geben Sie True zurück, andernfalls False. Beachten Sie, dass diese Klasse von der Trigger-Klasse erbt.
Time Trigger
Das Toot-Objekt enthält auch ein pubdate-Attribut. Wir können dieses nutzen, um nach dem Veröffentlichungsdatum von Toots zu filtern. Hierzu erstellen Sie im Folgenden eine TimeTrigger-Klasse.
Der TimeTrigger ist ein Trigger, der ausgelöst wird, basierend auf der Veröffentlichungszeit eines Toots.
Erstellen Sie eine Klasse TimeTrigger. Die Klasse soll einen Initialisierer enthalten, der zwei Parameter akzeptiert. Der erste ist ptime, einen String im Format "YYYY-MM-DD HH:MM:SS", der die gewünschte Zeit beschreibt.
Der zweite Parameter ist timezone. Ändern Sie diesen Parameter so ab, dass er eine optionale Angabe für die Zeitzone ist, die Sie standardmäßig auf EST setzen sollen. Wenn Ihnen unklar ist, wie Sie das machen sollen, dann können Sie dazu das unten stehende Hilfefenster (Hilfe zu Standardparametern) öffnen.
Der übergebene ptime-String soll in ein datetime-Objekt umgewandelt werden. Anschließend soll mithilfe der Bibliothek pytz die angegebene Zeitzone hinzugefügt werden, sodass ptime korrekt lokalisiert ist.
Da TimeTrigger eine abstrakte Klasse ist, ist keine Implementierung einer evaluate-Methode erforderlich.
astimezone, da sie den String nicht wie erforderlich formatiert.Hilfe zu Standardparametern
Einen Standardparameter können Sie wie folgt implementieren:
def my_function(parameter="bar")
print(f"foo, {parameter}")In diesem Beispiel sehen Sie eine Funktion my_function. Diese kann einen Parameter parameter erhalten und gibt diesen entsprechend auf der Konsole aus.
my_function kann nun wie folgt aufgerufen werden:
my_function("my value")Dabei erhalten Sie folgende Ausgabe auf der Konsole: foo, my value.
Dies unterscheidet sich von der Situation, wenn Sie my_function wie folgt aufrufen:
my_function()Hier erhalten Sie folgende Ausgabe auf der Konsole: foo, bar.
Es wird also der standardmäßige Wert bar für parameter genutzt.
Before-Trigger
Nun wollen wir die Unterklasse BeforeTrigger-Klasse der TimeTrigger-Klasse erstellen.
Der BeforeTrigger ist ein TimeTrigger, der ausgelöst wird, wenn ein Toot strikt vor der Auslösezeit veröffentlicht wurde. Die Auslösezeit (ptime) haben Sie bereits in der Superklasse spezifiziert.
Um diesen Trigger zu implementieren, müssen Sie den BeforeTrigger als Unterklasse von TimeTrigger erstellen.
Da der BeforeTriggervon TimeTrigger erbt, übernimmt er automatisch die __init__-Methode der Superklasse, was bedeutet, dass Sie diese Methode nicht erneut definieren müssen.
In der evaluate-Methode greifen Sie auf das pubdate-Attribut des Toot-Objekts zu, um die Veröffentlichungszeit des Toots zu erhalten.
Falls der Toot vor der Auslösezeit veröffentlicht wurde, geben Sie True zurück, andernfalls False.
After-Trigger
Der AfterTrigger ist ein TimeTrigger, der ausgelöst wird, wenn ein Toot strikt nach der Auslösezeit veröffentlicht wurde.
Um diesen Trigger zu implementieren, erstellen Sie die AfterTrigger-Klasse als Unterklasse der TimeTrigger-Klasse. Da der AfterTrigger dem BeforeTrigger sehr ähnlich ist, sollten Sie ähnliche Überlegungen wie bei der Implementierung des BeforeTrigger anstellen. In der evaluate-Methode greifen Sie auf das pubdate-Attribut des Toot-Objekts zu, um die Veröffentlichungszeit des Toots zu erhalten. Falls der Toot nach der Auslösezeit veröffentlicht wurde, geben Sie True zurück, andernfalls False.
Trigger-Kombinationen
Im Folgenden wollen wir einige Klassen implementieren, um unsere bereits implementierten Trigger-Klassen kombinieren zu können. Implementieren Sie hierfür die drei folgenden Klassen.
Not-Trigger
Der NotTrigger ist ein Trigger, der die Ausgabe eines anderen Triggers umkehrt.
Zur Implementierung wird beim Erstellen einer Instanz von NotTrigger ein anderer Trigger als Argument übergeben. Implementieren Sie die evaluate-Methode so, dass dieser übergebene Trigger für den jeweiligen Toot ausgewertet wird. Anschließend soll das Ergebnis umgekehrt werden: Wenn der ursprüngliche Trigger True zurückgibt, liefert der NotTrigger also False zurück, und umgekehrt.
And-Trigger
Der AndTriggerist ein Trigger, der für einen Toot nur dann ausgelöst wird, wenn beide übergebenen Trigger für diesen Toot auslösen.
Beim Erstellen einer Instanz von AndTrigger sollen zwei Trigger als Argument übergeben werden. Implementieren Sie die evaluate-Methode so, dass diese beiden Trigger für den jeweiligen Toot ausgewertet werden. Der AndTrigger gibt nur dann True zurück, wenn beide Trigger unabhängig voneinander ebenfalls True zurückgeben. In allen anderen Fällen wird False zurückgegeben.
Or-Trigger
Der OrTrigger ist ein Trigger, der für einen Toot ausgelöst wird, wenn mindestens einer (oder beide) der übergebenen Trigger für diesen Toot auslöst.
Beim Erstellen einer Instanz von OrTrigger sollen zwei Trigger als Argument übergeben werden. Implementieren Sie die evaluate-Methode so, dass geprüft wird, ob einer der beiden Trigger für den jeweiligen Toot True zurückgibt. Falls dies bei einem oder beiden Triggern der Fall ist, gibt der OrTrigger ebenfalls True zurück. Andernfalls wird False zurückgegeben.
Zwischenfazit
Gratulation! Sie haben nun alle wichtigen Bausteine für Ihre Mastodon-Filteranwendung implementiert. Das war ein ganzes Stück Arbeit, aber Sie haben dabei zentrale Konzepte der objektorientierten Programmierung wie Vererbung, abstrakte Klassen und Polymorphie nicht nur theoretisch kennengelernt, sondern auch praktisch angewendet. Lassen Sie uns nun alles zusammenfügen und Ihre Implementierung in Aktion sehen!
Anwendung
Im Folgenden wollen wir die zuvor implementierten Klassen nutzen. Dafür müssen wir noch eine Funktion implementieren und dann alles zusammenfügen.
Filtern der Toots
Nun soll die Funktion filter_toots implementiert werden. Sie dient dazu, Toots basierend auf den definierten Triggern zu filtern. Dafür wendet sie die implementierten Trigger auf die geladenen Toots an und gibt nur diejenigen zurück, die alle Filterkriterien erfüllen.
Erstellen Sie eine Funktion, die zwei Eingaben erwartet: eine Liste von Toots und eine Liste von Triggern, die Sie als trigger_list bezeichnen. Gehen Sie wie folgt vor: Durchlaufen Sie jeden Toot in der Toot-Liste und überprüfen Sie für jeden Toot, ob alle Trigger aus der trigger_list zutreffen. Wenden Sie dazu die evaluate-Methode jedes Triggers auf den jeweiligen Toot an. Falls alle Trigger True zurückgeben, soll der Toot in die Ergebnisliste aufgenommen werden.
Verwenden Sie dabei List-Comprehension, um die Liste der gefilterten Toots direkt zu erstellen. Am Ende gibt die Funktion diese Liste zurück, die nur die Toots enthält, die alle Bedingungen erfüllen.
Toots filtern und ausgeben
Nachdem Sie alle Trigger und die erforderlichen Funktionen der Aufgabenstellung implementiert haben, können Sie im Abschnitt if __name__ == '__main__': damit beginnen, Toots zu filtern. Gehen Sie dabei wie folgt vor:
- Toots laden: Laden Sie zuerst die Toots.
- Trigger und Trigger-Kombinationen definieren: Legen Sie anschließend die gewünschten Trigger und deren Zusammensetzungen fest. Achten Sie darauf, dass Ihre Trigger-Anordnung logisch aufgebaut ist und sich Trigger nicht gegenseitig blockieren. Zum Beispiel sollte ein
AndTriggernicht einenBeforeTrigger(01.01.2024) und einenAfterTrigger(01.01.2024) kombinieren, da diese sich gegenseitig ausschließen. - Triggerliste aufstellen: Erstellen Sie eine Liste aller Trigger, die Sie überprüfen möchten.
- Filter-Funktion aufrufen: Verwenden Sie die Liste aller Toots und die Triggerliste, um die Funktion
filter_tootsaufzurufen. - Gefilterte Toots ausgeben: Geben Sie abschließend die Toots, welche in der Liste
triggered_tootssind, auf der Konsole aus. Nutzen Sie hierfür die__str__-Methode derToot-Klasse, die Sie zum beginn der Aufgabe implementiert haben.
Testen
Laden Sie Toots basierend auf einem Hashtag herunter, wie im Abschnitt Toots filtern und ausgeben beschrieben, und erstellen Sie entsprechende Trigger-Klassen.
Führen Sie Ihr Programm mit dem folgenden Befehl aus, um die Funktionalität zu testen:
python mastodon_oop.pyPassen Sie anschließend die Trigger sowie deren Kombinationen an, um alle Funktionen Ihres Programms zu testen.
Style
Führen Sie den folgenden Befehl aus, um den Stil Ihres Codes mit style50 zu analysieren:
style50 mastodon_oop.pyGeschafft!
Sie haben es geschafft! Diese Übung war ohne Frage anspruchsvoll, aber Sie haben dabei viele wichtige Konzepte der Softwareentwicklung kennengelernt und praktisch umgesetzt. Von der API-Anbindung über objektorientierte Programmierung bis hin zu flexiblen Filtermechanismen – das sind Fähigkeiten, die Sie in vielen Programmierprojekten gut gebrauchen können.
Wir würden gerne von Ihnen lernen: An welchen Stellen der Aufgabe sind Sie besonders gut vorangekommen? Wo hätten Sie sich mehr Hilfestellung gewünscht? Ihr Feedback hilft uns, die Übung weiter zu verbessern. Nutzen Sie dafür gerne das Kursforum oder sprechen Sie uns in den Tutorien an.