paint-brush
Einen unendlich langen Aufsatz mit State Pattern in Python schreibenvon@aayn
2,382 Lesungen
2,382 Lesungen

Einen unendlich langen Aufsatz mit State Pattern in Python schreiben

von Aayush10m2023/12/27
Read on Terminal Reader

Zu lang; Lesen

Zustandsentwurfsmuster sind Zustandsmaschinen in objektorientiertem Code. Wir verwenden das Muster, um einen Aufsatz-/Satzgenerator zu erstellen.
featured image - Einen unendlich langen Aufsatz mit State Pattern in Python schreiben
Aayush HackerNoon profile picture
0-item

Lasst uns einen unendlich langen Aufsatz schreiben! Aber das ist eine unmögliche Aufgabe. Wir können jedoch einen Prozess erstellen, der, wenn er ewig läuft, einen unendlich langen Aufsatz erzeugen würde. Nah genug .


Jetzt können Sie natürlich einen langen und sich wiederholenden Aufsatz mit einer einzigen Zeile Python-Code erstellen:


 >>> "This is water. " * 20 'This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. This is water. '


Gähn … Buh! Stattdessen werden wir in diesem Artikel einen viel interessanteren Aufsatz erstellen, der das State-Entwurfsmuster verwendet.


Zunächst werden wir verstehen, was Zustandsmaschinen sind und wie sie mit dem Zustandsentwurfsmuster zusammenhängen. Als nächstes erstellen wir eine Zustandsmaschine, die einen (einigermaßen) interessanten und unendlichen Aufsatz generieren kann. Anschließend gehen wir kurz auf das staatliche Entwurfsmuster ein. Abschließend werden wir diese Zustandsmaschine mithilfe des Zustandsentwurfsmusters in objektorientierten Code übersetzen.


Software-Entwurfsmuster sind wirksame Methoden zur Lösung häufig auftretender Probleme. Bei richtiger Anwendung können Softwareentwurfsmuster wie das Zustandsmuster Ihnen dabei helfen, besser skalierbare, wartbare und testbare Software zu schreiben.

Zustandsmaschine

Im Wesentlichen übersetzt das State-Entwurfsmuster eine State Machine in objektorientierten Code.


Wenn Sie mit Zustandsautomaten nicht vertraut sind, handelt es sich um ein ziemlich einfaches Konzept. Eine Zustandsmaschine verfügt über Zustände und Übergänge . Zustände sind bestimmte Eigenschaften unseres interessierenden Systems, und Zustandsübergänge sind Aktionen, die diese Eigenschaften ändern und dadurch auch eine Zustandsänderung bewirken.


Da ich (unter anderem) einen Robotik-Hintergrund habe und Zustandsmaschinen in der Robotik häufig verwendet werden, werde ich ein einfaches Beispiel eines Roboterstaubsaugers verwenden, um zu veranschaulichen, wie Zustandsmaschinen funktionieren.


Zustandsmaschine zum Antrieb eines Roboterstaubsaugers.


Das Zustandsmaschinendiagramm vermittelt ein intuitives Bild der Funktionsweise des Roboters, auch wenn Sie noch nie mit Zustandsmaschinen in Berührung gekommen sind. Lassen Sie uns diesen Vorgang Schritt für Schritt durchgehen.


  1. Der Roboter startet im angedockten Zustand (der schwarze Punkt zeigt den Startzustand an).
  2. Wenn der Roboter feststellt, dass sein Akku fast leer ist, beginnt er, sich selbst aufzuladen ( Ladezustand ), bis der Akku voll ist. Sobald der Akku voll ist, kehrt er in den angedockten Zustand zurück.
  3. Wenn der Roboter im angedockten Zustand erkennt, dass der Boden verschmutzt ist (und seine Batterie nicht schwach ist), beginnt er mit der Reinigung des Bodens ( Reinigungszustand ).
  4. Wenn der Akku des Roboters im Reinigungszustand fast leer ist, lädt er sich selbst auf. Und wenn der Boden sauber ist, kehrt der Roboter zu seinem Dock zurück ( Angedockter Zustand).


Somit hat unser Roboterstaubsauger drei Zustände – Angedockt , Reinigen und Laden – und verfügt über Übergänge, die auf der sensorischen Erkennung des Bodens und seines Akkus basieren.


Einfache Zustandsmaschine für unendliche Aufsätze

Nachdem wir nun Zustandsautomaten auf einer grundlegenden Ebene verstanden haben, erstellen wir einen Zustandsautomaten, der in der Lage ist, einen unendlichen Aufsatz zu schreiben.


Zustandsmaschine zur Generierung eines unendlichen Aufsatzes – Version 1


Oben sehen Sie eine Zustandsmaschine, die englische Grammatik verwendet, um einen Aufsatz zu generieren, der aus kurzen, einfachen Sätzen besteht. Ich verspreche, dass wir sehr bald zu einer interessanteren Version kommen werden, aber dies sollte als guter Ausgangspunkt für das Verständnis dienen. Sehen wir uns an, wie es funktioniert.


  1. Ausgehend vom Zustand „Nomen“ generieren wir ein Substantiv, indem wir es aus einer vordefinierten Liste von Substantiven auswählen. Nehmen wir an, unser Substantiv ist „Die Welt“. (Satz bisher: „Die Welt“)
  2. Dann landen wir im Verb- Zustand und generieren als nächstes ein Verb (z. B. „bellt“). (Bisheriger Satz: „Die Welt bellt“)
  3. Wir generieren ein Adjektiv (z. B. „rot“) im Adjektivzustand . (Bisheriger Satz: „Die Welt bellt rot“)
  4. Dann generieren wir im Endmark- Zustand eines der abschließenden Satzzeichen, sagen wir „!“. (Satz: „Die Welt bellt rot!“)
  5. Schließlich sind wir wieder im Nomen- Zustand, um unseren nächsten Satz im Aufsatz zu bilden.


Diese Zustandsmaschine könnte einen (unsinnigen) Aufsatz erzeugen, der so aussieht.


Die Welt bellt rot! Cousin Harry regnet Foul? Die Tiger schimmern lustig. …


Nichtdeterministische Zustandsmaschine für unendliche Aufsätze

Obwohl „nicht deterministisch“ kompliziert klingt, bedeutet es für unsere Zwecke lediglich, etwas Zufälligkeit hinzuzufügen. Im Wesentlichen führen wir eine Art Münzwurf ein, bevor wir zu einigen Bundesstaaten wechseln. Du wirst sehen, was ich meine.

Nichtdeterministische Zustandsmaschine zur Generierung eines unendlichen Aufsatzes – Version 2


Der obige nichtdeterministische Zustandsautomat ist dem vorherigen sehr ähnlich. Die einzigen Unterschiede sind:

  • Verneinungen sind Wörter wie „nein“ oder „nicht“ und Konjunktionen sind Wörter wie „und“ und „aber“.
  • Im Verb- Zustand erzeugen wir ein Verb und werfen dann eine Münze. Wenn es „Kopf“ landet (Wahrscheinlichkeit 50 %), gehen wir in den Negationszustand über; andernfalls gehen wir in den Adjektivzustand über.
  • In ähnlicher Weise generieren wir im Adjektivzustand ein Adjektiv und werfen dann eine Münze. Bei „Kopf“ gehen wir in den Konjunktionszustand über; Wenn es Zahl ist, gehen wir in den Endmark- Zustand.


Mit der Einführung von Zufälligkeit, Negation und Konjunktionen können wir nun interessantere Sätze mit variabler Länge generieren.


Staatliches Designmuster

Lassen Sie uns nun verstehen, wie das staatliche Entwurfsmuster funktioniert. Denken Sie auch hier daran, dass wir versuchen, eine Zustandsmaschine in objektorientierten Code zu übersetzen.


Beachten Sie in der Zustandsmaschine zur Aufsatzgenerierung, dass jeder Staat zwei Dinge tun muss.

  1. Führen Sie eine Aktion aus. In diesem Fall wird ein Wort (Substantiv, Adjektiv usw.) generiert.
  2. Übergang zum nächsten Zustand. Vom Substantiv zum Verb und so weiter.


Aus der Sicht eines bestimmten Staates gibt es nichts anderes , was er wissen oder tun muss. Anstatt uns auf die Komplexität des gesamten Systems – all seiner Zustände und Übergänge – einzulassen, können wir uns immer nur auf einen Zustand nach dem anderen konzentrieren. Meiner Ansicht nach ist diese Art der Isolation und Entkopplung das größte Verkaufsargument des Staatsmusters.


Unten haben wir ein UML- Diagramm für das State-Entwurfsmuster. Es gibt einen bestimmten Kontext, in dem jeder der Zustände operiert, veranschaulicht durch die Context Klasse. Das Kontextobjekt verfügt über ein privates Statusattribut, mit dem es den aktuellen Status aufruft, um seine Aktion auszuführen. Jeder Zustand implementiert eine State mit Methoden zum Ausführen seiner Aktion oder Operation und zum Zurückgeben des nächsten Zustands.


Zustandsentwurfsmuster UML-Diagramm


Wenn wir dies auf das Beispiel zur Aufsatzgenerierung übertragen, sieht das UML-Diagramm so aus.


Zustandsmuster für die Aufsatzerstellung implementiert


WordState ist jetzt eine abstrakte Klasse (durch Kursivschrift gekennzeichnet) anstelle einer Schnittstelle. Abstrakte Klassen können einige abstrakte (nicht implementierte) Methoden und Attribute haben, während andere definiert werden können. Schnittstellen sind völlig abstrakt: Alle ihre Methoden sind abstrakt. Ich habe diese Änderung vorgenommen, weil die Implementierung generateWord in allen Bundesstaaten gleich ist und es sinnvoll ist, doppelten Code zu vermeiden.


Lassen Sie uns die einzelnen Attribute und Methoden oben aufschlüsseln. In der EssayContext Klasse haben wir:

  • state : Referenz auf das aktuelle WordState Objekt.
  • essayBody : Liste aller bisher generierten Wörter.
  • setState() : Setter zum Ändern des state .
  • addWord() : Methode zum Hinzufügen des nächsten Wortes zum Aufsatztext.
  • generateEssay() : Wir rufen diese Methode auf, um unseren Aufsatz zu generieren; Wir hören auf, wenn die Länge des essayBody größer als length ist.
  • printEssay() : Gibt eine Zeichenfolge des generierten Aufsatzes zurück.


In der abstrakten Klasse WordState haben wir:

  • wordList : Abstrakte Eigenschaft (kursiv dargestellt) für eine Liste von Wörtern, aus der wir Wörter zum Generieren auswählen.
  • generateWord() : Methode, die generierte Wörter zum Aufsatzkontext hinzufügt.
  • nextState() : Abstrakte Methode zur Rückgabe des nächsten Zustands.


Wir verwenden NounState als repräsentatives Beispiel für alle anderen konkreten Zustände, die von WordState geerbt werden.

  • wordList : Eine Liste von Substantiven, aus denen wir Wörter zur Generierung auswählen.
  • nextState() : Gibt den nächsten Zustand zurück.


Jetzt haben wir alles, was wir brauchen, um dies tatsächlich im Code zu implementieren. Lasst uns weitermachen und genau das tun!


Python-Code

Schreiben wir zunächst die EssayContext Klasse in eine Datei namens essay_context.py . Wir lassen den Kamelfall hinter uns und wechseln zum Schlangenfall, denn Python ist ja eine... Schlange (tut mir leid).


 from word_state import WordState class EssayContext: def __init__(self, state: WordState): self.state = state self.essay_body: list[str] = [] def set_state(self, state: WordState): self.state = state def add_word(self, word: str): self.essay_body.append(word) def generate_essay(self, length: int): while len(self.essay_body) < length: self.state.generate_word(self) def print_essay(self) -> str: return " ".join(self.essay_body)


Anschließend fügen wir die Zustände in einer Datei namens word_state.py hinzu.


 import abc import numpy as np class WordState(abc.ABC): word_list: list[str] @classmethod def generate_word(cls, context: "EssayContext"): word = np.random.choice(cls.word_list) context.add_word(word) context.set_state(cls.next_state()) @classmethod @abc.abstractmethod def next_state(cls) -> "WordState": pass class NounState(WordState): word_list: list[str] = ["everything", "nothing"] @classmethod def next_state(cls): return VerbState class VerbState(WordState): word_list: list[str] = ["is", "was", "will be"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return NegationState return AdjectiveState class NegationState(WordState): word_list: list[str] = ["not"] @classmethod def next_state(cls): return AdjectiveState class AdjectiveState(WordState): word_list: list[str] = ["fantastic", "terrible"] @classmethod def next_state(cls): heads = np.random.rand() < 0.5 if heads: return ConjunctionState return EndmarkState class ConjunctionState(WordState): word_list: list[str] = ["and", "but"] @classmethod def next_state(cls): return NounState class EndmarkState(WordState): word_list: list[str] = [".", "!"] @classmethod def next_state(cls): return NounState


Zum Schluss fügen wir Code hinzu, um alles in main.py auszuführen.


 from essay_context import EssayContext from word_state import NounState if __name__ == '__main__': ctx = EssayContext(NounState) ctx.generate_essay(100) print(ctx.print_essay())


Wenn wir python main.py ausführen, erhalten wir die folgende Ausgabe (aufgrund des Nichtdeterminismus jedes Mal unterschiedlich):


 'everything is not terrible and nothing was terrible ! everything will be not fantastic but everything is fantastic . everything will be fantastic . nothing will be fantastic and nothing will be terrible ! everything was not fantastic and everything will be not terrible . everything was terrible . nothing was terrible but nothing will be fantastic ! nothing is not terrible . nothing was not fantastic but everything was not fantastic ! everything will be not fantastic but everything will be terrible ! everything will be not fantastic . everything is fantastic but nothing will be not terrible ! everything will be not fantastic but nothing was not fantastic !'


Nicht schlecht für ein so einfaches System! Wir können auch die verschiedenen Wortlisten erweitern oder weitere Zustände hinzufügen, um die Aufsatzerstellung anspruchsvoller zu gestalten. Wir könnten sogar einige LLM-APIs einführen, um unsere Aufsätze auf die nächste Stufe zu heben.


Abschließende Gedanken

Zustandsmaschinen und das Zustandsmuster eignen sich hervorragend zum Modellieren und Erstellen von Systemen mit einer klar definierten Vorstellung von einem „Zustand“. Das heißt, mit jedem Zustand sind spezifische Verhaltensweisen und Eigenschaften verbunden. Der Saugroboter reinigt, ist angedockt oder lädt. Ihr Fernseher kann ein- oder ausgeschaltet sein und die Fernbedienungstasten des Fernsehers verhalten sich je nach Zustand des Fernsehers unterschiedlich.


Es eignet sich auch gut zum Generieren oder Identifizieren von Sequenzen mit einem genau definierten Muster. Dies gilt für unser Beispiel zur Aufsatzerstellung.


Abschließend fragen Sie sich vielleicht: „Was hat das alles für einen Sinn?“ Warum haben wir uns die Mühe gemacht, die verschiedenen Zustände und Klassen zu definieren, um diesen „unendlichen“ Aufsatz zu erstellen? Wir hätten 20 (oder weniger) Zeilen Python-Code schreiben können, um das gleiche Verhalten zu erreichen.


Die kurze Antwort lautet: bessere Skalierbarkeit .


Stellen Sie sich vor, wir hätten statt nur drei oder fünf Staaten 50 oder 500 Staaten. Das ist keine Übertreibung; Echte Unternehmenssysteme weisen diesen Grad an Komplexität auf. Plötzlich scheint das Staatsmuster aufgrund seiner Entkopplung und Isolation viel attraktiver zu sein. Wir können uns jeweils nur auf einen Zustand konzentrieren, ohne das gesamte System im Kopf behalten zu müssen. Es ist einfacher, Änderungen einzuführen, da ein Status keine Auswirkungen auf andere hat. Es ermöglicht auch einfachere Unit-Tests, ein großer Teil eines skalierbaren und wartbaren Systems.


Letztlich geht es beim Staatsmuster nicht nur um die Verwaltung von Staaten; Wie alle Entwurfsmuster ist es eine Blaupause für den Aufbau von Systemen, die ebenso skalierbar und wartbar wie anspruchsvoll sind.