paint-brush
Escribir un ensayo infinitamente largo usando un patrón de estado en Pythonpor@aayn
2,385 lecturas
2,385 lecturas

Escribir un ensayo infinitamente largo usando un patrón de estado en Python

por Aayush10m2023/12/27
Read on Terminal Reader

Demasiado Largo; Para Leer

El patrón de diseño de estados son máquinas de estados en código orientado a objetos. Usamos el patrón para crear un generador de ensayos/oraciones.
featured image - Escribir un ensayo infinitamente largo usando un patrón de estado en Python
Aayush HackerNoon profile picture
0-item

¡Escribamos un ensayo infinitamente largo ! Pero esa es una tarea imposible. Sin embargo, podemos crear un proceso que, si se ejecutara eternamente, generaría un ensayo infinitamente largo. Suficientemente cerca .


Ahora, obviamente puedes generar un ensayo largo y repetitivo con una sola línea de código Python:


 >>> "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. '


Bostezo … ¡abucheo! En cambio, generaremos un ensayo mucho más interesante utilizando el patrón de diseño Estado en este artículo.


Primero, entenderemos qué son las máquinas de estados y cómo se relacionan con el patrón de diseño de estados. A continuación, crearemos una máquina de estados que pueda generar un ensayo infinito (razonablemente) interesante. Luego, repasaremos brevemente el patrón de diseño estatal. Finalmente, traduciremos esa máquina de estados a código orientado a objetos utilizando el patrón de diseño de estados.


Los patrones de diseño de software son formas efectivas de resolver problemas que ocurren comúnmente. Cuando se aplican adecuadamente, los patrones de diseño de software, como el patrón de estado, pueden ayudarlo a escribir un software mejor escalable, mantenible y comprobable.

Máquina estatal

En esencia, el patrón de diseño State traduce una máquina de estados en código orientado a objetos.


Si no está familiarizado con las máquinas de estados, es un concepto bastante simple. Una máquina de estados tiene estados y transiciones . Los estados son ciertas propiedades de nuestro sistema de interés, y las transiciones de estado son acciones que cambian estas propiedades y, por lo tanto, también causan un cambio de estado.


Como tengo experiencia en robótica (entre otras cosas) y debido a que las máquinas de estados se usan ampliamente en robótica, usaré un ejemplo simple de un robot aspirador para ilustrar cómo funcionan las máquinas de estados.


Máquina de estados para accionar un robot aspirador.


El diagrama de la máquina de estados muestra una imagen intuitiva de cómo opera el robot, incluso si nunca se ha encontrado con máquinas de estados. Repasemos esta operación paso a paso.


  1. El robot se inicia en el estado Atracado (el punto negro indica el estado inicial).
  2. Si el robot detecta que su batería está baja, comienza a cargarse solo (estado de carga ) hasta que la batería esté llena. Una vez que la batería está llena, vuelve al estado acoplado .
  3. En el estado Acoplado , si el robot detecta que el piso está sucio (y su batería no está baja), comienza a limpiar el piso (estado de limpieza ).
  4. En el estado de limpieza , si el robot tiene poca batería, se cargará solo. Y si el suelo está limpio, el robot vuelve a su base (estado Docked ).


Así, nuestro robot aspirador tiene tres estados ( Atracado , Limpiando y Cargando ) y tiene transiciones basadas en la detección sensorial del suelo y su batería.


Máquina de estados simple para ensayo infinito.

Ahora que entendemos las máquinas de estados en un nivel básico, creemos una máquina de estados capaz de escribir un ensayo infinito.


Máquina de estados para generar ensayos infinitos - versión 1


Arriba hay una máquina de estados que utiliza la gramática inglesa para generar un ensayo compuesto por oraciones cortas y simples. Prometo que llegaremos a una versión más interesante muy pronto, pero esto debería servir como un buen punto de partida para la comprensión. Repasemos cómo funciona.


  1. Comenzando en el estado Sustantivo , generamos un sustantivo seleccionándolo de una lista predefinida de sustantivos. Digamos que nuestro sustantivo es “El Mundo”. (frase hasta el momento: “El Mundo”)
  2. Luego terminamos en el estado Verbo , generando un verbo a continuación (digamos, “ladra”). (frase hasta el momento: “El mundo ladra”)
  3. Generamos un adjetivo (digamos, "rojo") en el estado Adjetivo . (frase hasta el momento: “El mundo ladra rojo”)
  4. Luego, en el estado Endmark , generamos uno de los signos de puntuación finales, digamos "!". (frase: “¡El mundo ladra rojo!”)
  5. Finalmente, volvemos al estado Sustantivo para generar nuestra siguiente oración en el ensayo.


Esta máquina de estados podría generar un ensayo (sin sentido) similar a este.


¡El mundo ladra rojo! ¿El primo Harry llueve mal? Los tigres brillan de forma divertida. …


Máquina de estados no determinista para ensayo infinito

Aunque “no determinista” suena complicado, para nuestros propósitos simplemente significa agregar algo de aleatoriedad. Básicamente, estamos agregando una especie de lanzamiento de moneda antes de hacer la transición a algunos de los estados. Verás lo que quiero decir.

Máquina de estados no determinista para generar ensayos infinitos - versión 2


La máquina de estados no determinista anterior es muy similar a la anterior. Las únicas diferencias son:

  • Las negaciones son palabras como "no" o "no" y las conjunciones son palabras como "y" y "pero".
  • En el estado Verbo , generamos un verbo y luego lanzamos una moneda. Si sale cara (50% de probabilidad), pasamos al estado de Negación ; de lo contrario pasamos al estado Adjetivo .
  • De manera similar, en el estado Adjetivo , generamos un adjetivo y luego lanzamos una moneda. Si sale cara, pasamos al estado de Conjunción ; si es cruz, entonces pasamos al estado Endmark .


Con la introducción de la aleatoriedad, la negación y las conjunciones, ahora podemos generar oraciones más interesantes y de longitud variable.


Patrón de diseño estatal

Ahora, comprendamos cómo funciona el patrón de diseño estatal. Nuevamente, recuerde que estamos intentando traducir una máquina de estados a código orientado a objetos.


En la máquina de estados de generación de ensayos, observe que cada estado necesita hacer dos cosas.

  1. Realizar alguna acción. En este caso, generando una palabra (sustantivo, adjetivo, etc.).
  2. Transición al siguiente estado. Del sustantivo al verbo , etcétera.


Desde el punto de vista de un Estado en particular, no hay nada más que deba saber o hacer. En lugar de quedar estancados por la complejidad de todo el sistema (todos sus estados y transiciones), podemos centrarnos simplemente en un estado a la vez. En mi opinión, este tipo de aislamiento y desacoplamiento es el mayor atractivo del modelo estatal.


A continuación, tenemos un diagrama UML para el patrón de diseño State. Hay algún contexto en el que opera cada uno de los estados, ilustrado por la clase Context . El objeto de contexto tiene un atributo de estado privado, que utiliza para llamar al estado actual para realizar su acción. Cada estado implementa una interfaz State con métodos para realizar su acción u operación y devolver el siguiente estado.


Diagrama UML del patrón de diseño de estado


Si asignamos esto al ejemplo de generación de ensayos, el diagrama UML se verá así.


Patrón de estado implementado para la generación de ensayos.


WordState es ahora una clase abstracta (indicada en cursiva) en lugar de una interfaz. Las clases abstractas pueden tener algunos métodos y atributos abstractos (no implementados), mientras que otros pueden definirse. Las interfaces son totalmente abstractas: todos sus métodos son abstractos. Hice este cambio porque la implementación generateWord es la misma en todos los estados y es bueno evitar el código duplicado.


Analicemos cada uno de los atributos y métodos anteriores. En la clase EssayContext , tenemos:

  • state : Referencia al objeto WordState actual.
  • essayBody : Lista de todas las palabras generadas hasta el momento.
  • setState() : Configurador para cambiar el atributo state .
  • addWord() : método para agregar la siguiente palabra al cuerpo del ensayo.
  • generateEssay() : llamamos a este método para generar nuestro ensayo; Nos detenemos cuando el essayBody tiene una longitud mayor que length .
  • printEssay() : Devuelve una cadena del ensayo generado.


En la clase abstracta WordState , tenemos:

  • wordList : Propiedad abstracta (indicada en cursiva) para una lista de palabras entre las cuales elegimos las palabras para generar.
  • generateWord() : método que agrega la palabra generada al contexto del ensayo.
  • nextState() : método abstracto para devolver el siguiente estado.


Usaremos NounState como ejemplo representativo de todos los demás estados concretos heredados de WordState .

  • wordList : una lista de sustantivos de la cual elegimos palabras para generar.
  • nextState() : Devuelve el siguiente estado.


Ahora tenemos todo lo que necesitamos para implementar esto en el código. ¡Sigamos adelante y hagamos justamente eso!


Código Python

Primero escribamos la clase EssayContext en un archivo llamado essay_context.py . Nos desharemos del caso del camello y cambiaremos al caso de la serpiente porque, bueno, Python es una... serpiente (lo siento).


 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)


Luego, agreguemos los estados en un archivo llamado word_state.py .


 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


Finalmente, agreguemos código para ejecutar todo en main.py


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


La ejecución python main.py nos da el siguiente resultado (diferente cada vez debido al no determinismo):


 '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 !'


¡Nada mal para un sistema tan simple! También podemos ampliar las distintas listas de palabras o agregar más estados para hacer que la generación del ensayo sea más sofisticada. Incluso podríamos presentar algunas API de LLM para llevar nuestros ensayos al siguiente nivel.


Pensamientos finales

Las máquinas de estados y el patrón de estado son ideales para modelar y crear sistemas con una noción bien definida de "estado". Es decir, existen comportamientos y propiedades específicas asociadas a cada estado. El robot aspirador está limpiando, acoplado o cargándose. Su televisor puede estar encendido o apagado, y los botones del control remoto del televisor actuarán de manera diferente según el estado del televisor.


También es una buena opción para generar o identificar secuencias con un patrón bien definido. Esto se aplica a nuestro ejemplo de generación de ensayos.


Finalmente, podrías preguntarte: “¿Cuál es el sentido de todo esto?” ¿Por qué nos tomamos la molestia de definir los distintos estados y clases para generar este ensayo “infinito”? Podríamos haber escrito 20 (o menos) líneas de código Python para lograr el mismo comportamiento.


La respuesta corta es para una mejor escalabilidad .


Imagínese si, en lugar de sólo tres o cinco estados, tuviéramos 50 o 500 estados. Esto no es una hipérbole; Los sistemas empresariales reales tienen ese nivel de complejidad. De repente, el patrón del Estado parece mucho más atractivo debido a su desacoplamiento y aislamiento. Podemos concentrarnos en un estado a la vez sin tener que mantener todo el sistema en nuestras cabezas. Es más fácil introducir cambios ya que un estado no afectará a los demás. También permite realizar pruebas unitarias más sencillas, una gran parte de un sistema escalable y mantenible.


En última instancia, el patrón estatal no se trata sólo de gestionar estados; Como todos los patrones de diseño, es un modelo para construir sistemas que sean tan escalables y mantenibles como sofisticados.