paint-brush
Escrevendo um ensaio infinitamente longo usando padrão de estado em Pythonpor@aayn
2,426 leituras
2,426 leituras

Escrevendo um ensaio infinitamente longo usando padrão de estado em Python

por Aayush Naik10m2023/12/27
Read on Terminal Reader

Muito longo; Para ler

O padrão de design de estado são máquinas de estado em código orientado a objetos. Usamos o padrão para criar um gerador de ensaio/frase.
featured image - Escrevendo um ensaio infinitamente longo usando padrão de estado em Python
Aayush Naik HackerNoon profile picture
0-item

Vamos escrever um ensaio infinitamente longo ! Mas essa é uma tarefa impossível. Porém, podemos criar um processo que, se executado para sempre, geraria um ensaio infinitamente longo. Perto o suficiente .


Agora, obviamente, você pode gerar um ensaio longo e repetitivo com uma única linha 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. '


Booawn … vaias! Em vez disso, geraremos um ensaio muito mais interessante usando o padrão de projeto State neste artigo.


Primeiro, entenderemos o que são máquinas de estado e como elas estão relacionadas ao padrão de design State. A seguir, criaremos uma máquina de estados que pode gerar um ensaio (razoavelmente) interessante e infinito. Em seguida, examinaremos brevemente o padrão de design do estado. Finalmente, traduziremos essa máquina de estados em código orientado a objetos usando o padrão de design de estados.


Os padrões de projeto de software são formas eficazes de resolver problemas comuns. Quando aplicados adequadamente, os padrões de design de software, como o padrão de estado, podem ajudá-lo a escrever software melhor escalável, sustentável e testável.

Máquina de Estado

Em essência, o padrão de design State traduz uma máquina de estados em código orientado a objetos.


Se você não está familiarizado com máquinas de estado, é um conceito bastante simples. Uma máquina de estados possui estados e transições . Os estados são certas propriedades do nosso sistema de interesse, e as transições de estado são ações que alteram essas propriedades e, portanto, também causam uma mudança de estado.


Como tenho experiência em robótica (entre outras coisas) e como as máquinas de estado são amplamente utilizadas em robótica, usarei um exemplo simples de aspirador de pó robô para ilustrar como as máquinas de estado funcionam.


Máquina estatal para acionar um aspirador robô.


O diagrama da máquina de estados mostra uma imagem intuitiva de como o robô opera, mesmo que você nunca tenha encontrado máquinas de estados. Vamos repassar esta operação passo a passo.


  1. O robô inicia no estado Ancorado (o ponto preto indica o estado inicial).
  2. Se o robô detectar que a bateria está fraca, ele começa a carregar (estado de carregamento ) até que a bateria esteja cheia. Quando a bateria estiver cheia, ela retornará ao estado encaixado .
  3. No estado encaixado , se o robô detectar que o chão está sujo (e a bateria não está fraca), ele começa a limpar o chão (estado de limpeza ).
  4. No estado Limpeza , se o robô ficar com pouca bateria, ele irá carregar sozinho. E se o chão estiver limpo, o robô retorna ao seu encaixe (estado encaixado ).


Assim, nosso aspirador robô possui três estados — Ancorado , Limpeza e Carregamento — e possui transições baseadas na detecção sensorial do chão e de sua bateria.


Máquina de estado simples para ensaio infinito

Agora que entendemos as máquinas de estado em um nível básico, vamos criar uma máquina de estado capaz de escrever um ensaio infinito.


Máquina de estados para gerar ensaio infinito - versão 1


Acima está uma máquina de estado que usa a gramática inglesa para gerar uma redação composta por frases curtas e simples. Prometo que chegaremos a uma versão mais interessante muito em breve, mas isto deve servir como um bom ponto de partida para a compreensão. Vamos ver como funciona.


  1. Começando no estado Substantivo , geramos um substantivo escolhendo em alguma lista predefinida de substantivos. Digamos que nosso substantivo seja “O Mundo”. (frase até agora: “O Mundo”)
  2. Então terminamos no estado Verbo , gerando um verbo a seguir (digamos, “latido”). (frase até agora: “O mundo late”)
  3. Geramos um adjetivo (digamos, “vermelho”) no estado Adjetivo . (frase até agora: “O mundo late vermelho”)
  4. Então, no estado Endmark , geramos um dos sinais de pontuação final, digamos “!”. (frase: “O mundo late vermelho!”)
  5. Finalmente, estamos de volta ao estado Substantivo para gerar nossa próxima frase no ensaio.


Esta máquina de estado pode gerar um ensaio (absurdo) parecido com este.


O mundo late em vermelho! Primo Harry chove muito? Os tigres brilham divertidos. …


Máquina de estado não determinística para ensaio infinito

Embora “não determinístico” pareça complicado, para nossos propósitos, significa apenas adicionar alguma aleatoriedade. Essencialmente, estamos adicionando uma espécie de sorteio antes de fazer a transição para alguns dos estados. Você verá o que quero dizer.

Máquina de estados não determinística para gerar ensaio infinito - versão 2


A máquina de estado não determinística acima é muito semelhante à anterior. As únicas diferenças são:

  • Negações são palavras como “não” ou “não” e conjunções são palavras como “e” e “mas”.
  • No estado Verbo , geramos um verbo e depois jogamos uma moeda. Se sair cara (50% de probabilidade), passamos para o estado Negação ; caso contrário, vamos para o estado Adjetivo .
  • Da mesma forma, no estado Adjetivo , geramos um adjetivo e depois jogamos uma moeda. Se der cara, vamos para o estado de Conjunção ; se der coroa, então vamos para o estado Endmark .


Com a introdução da aleatoriedade, da negação e das conjunções, agora podemos gerar sentenças mais interessantes e de comprimento variável.


Padrão de Design de Estado

Agora, vamos entender como funciona o padrão de design de estado. Novamente, lembre-se de que estamos tentando traduzir uma máquina de estados em código orientado a objetos.


Na máquina de estado de geração de ensaios, observe que cada estado precisa fazer duas coisas.

  1. Execute alguma ação. Neste caso, gerando uma palavra (substantivo, adjetivo, etc.).
  2. Transição para o próximo estado. Do substantivo ao verbo e assim por diante.


Do ponto de vista de um determinado Estado, não há mais nada que ele precise saber ou fazer. Em vez de ficarmos atolados na complexidade de todo o sistema – todos os seus estados e transições – podemos concentrar-nos apenas num estado de cada vez. Na minha opinião, este tipo de isolamento e dissociação é o maior argumento de venda do padrão estatal.


Abaixo, temos um diagrama UML para o padrão de design State. Existe algum contexto no qual cada um dos estados opera, ilustrado pela classe Context . O objeto de contexto possui um atributo de estado privado, que ele usa para chamar o estado atual para executar sua ação. Cada estado implementa uma interface State com métodos para executar sua ação ou operação e retornar o próximo estado.


Diagrama UML do padrão de design de estado


Se mapearmos isso no exemplo de geração de ensaio, o diagrama UML ficará assim.


Padrão de estado implementado para geração de ensaios


WordState agora é uma classe abstrata (indicada em itálico) em vez de uma interface. Classes abstratas podem ter alguns métodos e atributos abstratos (não implementados), enquanto outros podem ser definidos. As interfaces são totalmente abstratas: todos os seus métodos são abstratos. Fiz essa alteração porque a implementação generateWord é a mesma em todos os estados e é bom evitar código duplicado.


Vamos detalhar cada um dos atributos e métodos acima. Na classe EssayContext , temos:

  • state : referência ao objeto WordState atual.
  • essayBody : Lista de todas as palavras geradas até agora.
  • setState() : Setter para alterar o atributo state .
  • addWord() : Método para adicionar a próxima palavra ao corpo do ensaio.
  • generateEssay() : chamamos esse método para gerar nossa redação; paramos quando o essayBody tem comprimento maior que length .
  • printEssay() : Retorna uma string da redação gerada.


Na classe abstrata WordState , temos:

  • wordList : propriedade abstrata (indicada em itálico) para uma lista de palavras da qual escolhemos palavras para gerar.
  • generateWord() : Método que adiciona a palavra gerada ao contexto da redação.
  • nextState() : método abstrato para retornar o próximo estado.


Usaremos NounState como exemplo representativo para todos os outros estados concretos herdados de WordState .

  • wordList : uma lista de substantivos da qual escolhemos palavras para gerar.
  • nextState() : Retorna o próximo estado.


Agora, temos tudo o que precisamos para realmente implementar isso no código. Vamos em frente e fazer exatamente isso!


Código Python

Vamos primeiro escrever a classe EssayContext em um arquivo chamado essay_context.py . Vamos abandonar o caso do camelo e mudar para o caso da cobra porque, bem, Python é uma... cobra (desculpe).


 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)


Então, vamos adicionar os estados em um arquivo chamado 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, vamos adicionar código para executar tudo em 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())


A execução python main.py nos fornece a seguinte saída (diferente a cada vez devido ao não 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 um sistema tão simples! Também podemos estender as várias listas de palavras ou adicionar mais estados para tornar a geração da redação mais sofisticada. Poderíamos até introduzir algumas APIs LLM para levar nossos ensaios ao próximo nível.


Pensamentos finais

As máquinas de estado e o padrão State são ideais para modelar e criar sistemas com uma noção bem definida de “estado”. Ou seja, existem comportamentos e propriedades específicas associadas a cada estado. O aspirador robô está a limpar, encaixado ou a carregar. Sua TV pode estar LIGADA ou DESLIGADA e os botões do controle remoto da TV funcionarão de maneira diferente com base no estado da TV.


Também é uma boa opção para gerar ou identificar sequências com um padrão bem definido. Isso se aplica ao nosso exemplo de geração de ensaio.


Finalmente, você pode perguntar: “Qual é o sentido de tudo isso?” Por que tivemos tantos problemas para definir os vários estados e classes para gerar este ensaio “infinito”? Poderíamos ter escrito 20 (ou menos) linhas de código Python para obter o mesmo comportamento.


A resposta curta é para melhor escalabilidade .


Imagine se, em vez de apenas três ou cinco estados, tivéssemos 50 ou 500 estados. Isso não é uma hipérbole; sistemas empresariais reais têm esse nível de complexidade. De repente, o padrão estatal parece muito mais atraente devido à sua dissociação e isolamento. Podemos concentrar-nos apenas num estado de cada vez, sem ter de manter todo o sistema nas nossas cabeças. É mais fácil introduzir mudanças porque um estado não afetará outros. Também permite testes de unidade mais fáceis, uma grande parte de um sistema escalável e de fácil manutenção.


Em última análise, o padrão estatal não se trata apenas de gerir estados; como todos os padrões de projeto, é um modelo para a construção de sistemas que sejam tão escaláveis e de fácil manutenção quanto sofisticados.