I am implementing a game in python, which will give the user a sample encryption, and explain each step from plaintext to cipher. The task for the player is to decrypt a word by undoing each step shown above on the new word.For this, I have created an abstract algorithm which requires an encrypt function, and a current development status. In the future I think it will require other properties such as explanation text or a difficulty level.The abstract algorithm looks like this:from abc import ABC, abstractmethodfrom enum import Enumclass Status(Enum):    The current development status    in_development = 0    under_testing = 1    production_ready = 2    known_bugs = 3class BaseAlgorithm(ABC):    This is the base on which all other algs are to be built    @abstractmethod    def encrypt(self, word):        Encrypt the word using an internal alg        pass    @abstractmethod    def decrypt(self, word):        Decrypt the word using an internal alg        If this is not overloaded, it is assumed that        return self.encrypt(word)    @property    @abstractmethod    def status(self):        The current progress and readiness of the alg        passAn example algorithm is implemented in rotate.pyfrom . import BaseAlgorithm, Statusclass RotateAlg(BaseAlgorithm):    Takes a rotation amount  which it uses to encrypt or decrypt    all words pass into it    def __init__(self, offset):        try:            self.offset = int(offset)        except ValueError:            raise ValueError(the offset must be an integer) from None        # Decides which direction is the default way to rotate        self.rotate_left = True    def encrypt(self, word):        Encrypts by rotating the word        return self.rotate(word)    def decrypt(self, word):        Decrypts by rotating the word back        return self.rotate(word, inverse=True)    def rotate(self, word, inverse=False):        Rotates the word to the left        offset = self.offset        # Inverse and rotate_left both flip the direction        # If both are the same the direction remains unchanged        if inverse == self.rotate_left:            offset *= -1        return rotate(word, offset)    @property    def status(self):        return Status.in_developmentdef rotate(word, offset):    Rotates the word right by the amount specified    offset %= len(word)    return word[offset:] + word[:offset]Finally how these algorithms would be composed together is a simple accumulating loopdef decode(cipher, encryptions):    for encryption in reversed(encryptions):        cipher = encryption.decrypt(cipher)    return cipherI feel like this is the wrong approach to take though, as there is a lot of boilerplate for what is essentially a function which is used in two ways. Is there a better design pattern to use in this scenario? I suspect a more functional approach would suit, such as passing the encryption functions into a class that binds them together. My concern is I lose the option of providing metadata with the function with that style.
Framework for composing algorithms
python;object oriented;design patterns
For such a simple use case, I would drop the abc module in favor of NotImplementedError:class BaseAlgorithm:    This is the base on which all other algorithms are to be built.    # Unless explicitly stated otherwise, algorithms are in development    STATUS = Status.in_development    def encrypt(self, word):        Encrypt the word using an internal algorithm.        raise NotImplementedError    def decrypt(self, word):        Decrypt the word using an internal algorithm.        If this is not overloaded, it is assumed that        encrypting and decrypting are the same operation.                return self.encrypt(word)Using such base, only encrypt is explicitly required to be overloaded. I also extracted the status from a method to a class constant as this is the kind of information that does not depend of any state about this class. It also allows to focus on the algorithm first and only change the status when it is ready. Lastly, I also expanded the docstrings a bit so they are more explicit; you may want to explain, in the class docstring, what a user is expected to do when inheriting from BaseAlgorithm.As for RotateAlg:you could name it Rotate as the Alg part is already implied from the base BaseAlgorithm class;you could incorporate the rotate function directly in the rotate method as there shouldn't be much use for a function on their own;you could handle the rotate_left logic better by using this information directly as a parameter to rotate.This would become something along the lines of:class Rotate(BaseAlgorithm):    Takes a rotation amount  which it uses to encrypt or decrypt    all words pass into it    def __init__(self, offset):        try:            self.offset = int(offset)        except ValueError:            raise ValueError(the offset must be an integer) from None    def encrypt(self, word):        Encrypts by rotating the word        return self.rotate(word, rotate_left=False)    def decrypt(self, word):        Decrypts by rotating the word back        return self.rotate(word, rotate_left=True)    def rotate(self, word, rotate_left=False):        Rotates the word to the chosen direction        offset = -self.offset if rotate_left else self.offset        offset %= len(word)        return word[offset:] + word[:offset]And, unless you specify otherwise with:class Rotate(BaseAlgorithm):    STATUS = Status.under_testing    ...then Rotate.STATUS will still be Status.in_development.