[PYTHON] J'ai créé un package extenum qui étend enum

Contexte

Le module enum a été ajouté à partir de Python 3.4. Un package rétroporté pour les versions inférieures à 3.4 (y compris 2.x) est enregistré auprès de PyPI sous le nom enum34.

Si vous n'avez jamais utilisé le module enum, l'article suivant vous sera utile.

Une note de Nick Coghlan, le développeur principal de CPython, fournit un aperçu de la bibliothèque standard d'énum et un résumé des discussions à l'époque.

L'énumération de Python peut être satisfaisante avec * Good Enough * car elle n'était pas standard à l'origine.

Au contraire, que manque-t-il? Il existe un livre intitulé Effective Java qui ressemble à une bible dans le monde Java, et il couvre un chapitre sur les meilleures pratiques d'énum. Je vais.

Dans le monde Java, enum est souvent utilisé car il a une large gamme d'applications et est une fonction pratique, comme garantir la sécurité du type au moment de la compilation et implémenter un singleton. Donc, tout en regardant Effective Java, j'ai essayé d'étendre les fonctions que Python enum n'a pas.

extenum

J'ai créé un package extenum qui étend le module enum standard.

Comment installer

$ pip install extenum

le terme

Selon Terminologie du document officiel

>> from enum import Enum
>> class Color(Enum):

... red = 1 ... green = 2 ... blue = 3 ...

 > Termes de commentaire
 > * La classe `Color` est un type d'énumération (ou Enum).
 > * Les attributs `Color.red`,` Color.green` et autres sont membres du type énumération (ou membres Enum).
 > * Les membres du type énumération ont un nom et une valeur (`Color.red` a un nom de` red`,` Color.blue` a une valeur de 3, etc.).

 Les constantes définies par le type d'énumération sont définies comme *** membres de type d'énumération (ou membres d'énumération) ***. Dans d'autres langues, il est également appelé un énumérateur ou une constante d'énumération. Je pense que les constantes d'énumération sont plus faciles à comprendre car je m'attends à ce qu'elles jouent un rôle en tant que constantes, mais conformément à la documentation officielle, je ferai référence aux constantes de type énumération en tant que membres de type énumération dans cet article.

### Implémentation de méthode fixe constante
 Le Java effectif est nommé comme une constante, mais cette * constante * fait référence à un membre du type énumération.

 La raison pour laquelle j'ai décidé de faire de l'extenum était que ce mécanisme n'était pas disponible. Lorsque j'ai recherché des méthodes constantes fixes, j'ai trouvé [asym-enum](https://github.com/AsymmetricVentures/asym-enum#methods-and-functions) comme quelque chose comme ça.


#### **`3.4`**
```python

from asymm_enum.enum import Enum

class E(Enum):
    A = 1
    B = 2

    def get_label(self):
        ''' instance methods are attached to individual members '''
        return self.label

    @classmethod
    def get_b_label(cls):
        ''' class methods are attached to the enum '''
        return cls.B.label

J'ai pensé que c'était un peu moche et j'ai décidé de le faire moi-même.

Avec extenum, vous pouvez l'implémenter comme ceci:

constant_specific_enum.py


from extenum import ConstantSpecificEnum

class Operation(ConstantSpecificEnum):
    PLUS = '+'
    MINUS = '-'
    TIMES = '*'
    DIVIDE = '/'

    @overload(PLUS)
    def apply(self, x, y):
        return x + y

    @overload(MINUS)
    def apply(self, x, y):
        return x - y

    @overload(TIMES)
    def apply(self, x, y):
        return x * y

    @overload(DIVIDE)
    def apply(self, x, y):
        return x / y

J'ai pu le mettre en œuvre proprement.

Puisque Python n'a pas de mécanisme de surcharge, * @ overload (CONSTANT) * implémente une méthode de constante fixe avec un décorateur.

3.4


>>> from constant_specific_enum import Operation
>>> for name, const in Operation.__members__.items():
...     print(name, ':', const.apply(2, 4))
PLUS : 6
MINUS : -2
TIMES : 8
DIVIDE : 0.5

Les méthodes à constante fixe sont également utiles pour communiquer l'intention par le nom de la méthode. Par exemple, examinons un type d'énumération qui contient des informations sur l'état du traitement.

3.4


from extenum import ConstantSpecificEnum

class Status(ConstantSpecificEnum):
    PREPARING = 1
    WAITING = 2
    RUNNING = 3

    @overload(PREPARING)
    def is_cancelable(self):
        return False

    @overload(WAITING)
    def is_cancelable(self):
        return True

    @overload(RUNNING)
    def is_cancelable(self):
        return True

Supposons que chaque membre d'énumération pointe vers une valeur de statut, mais vous devez déterminer s'il s'agit d'un statut annulable avant de pouvoir annuler ce processus.

S'il n'y a pas de méthode de constante fixe, nous vérifions si le membre du type d'énumération passé à une fonction est soit * WAITING * soit * RUNNING *.

3.4


def cancel(status):
    if status in [Status.WAITING, Status.RUNNING]:
        # do cancel

Cela nécessite que l'implémenteur ou le lecteur de la fonction d'annulation connaisse les détails du type d'énumération des informations d'état, et peut avoir besoin de modifier les conditions de cette fonction à mesure que les informations d'état augmentent.

Si vous avez une méthode de constante fixe, le jugement de statut du prétraitement de cette fonction d'annulation peut être écrit plus naturellement et proprement.

3.4


def cancel(status):
    if status.is_cancelable():
        # do cancel

Par rapport au code ci-dessus, il est plus facile pour les implémenteurs et les lecteurs de la fonction d'annulation de comprendre, et même si les informations d'état augmentent, il est seulement nécessaire d'ajouter une méthode de constante fixe énumérée.

Modèle d'énumération de stratégie

Ceci est un exemple d'application d'implémentation de méthode fixe constante. Il est destiné à une implémentation plus conservatrice en imbriquant les types d'énumération des attributs jour et jour de la semaine / jour férié.

strategy_enum_pattern.py


from extenum import ConstantSpecificEnum

class PayrollDay(ConstantSpecificEnum):

    class PayType(ConstantSpecificEnum):
        WEEKDAY = 1
        WEEKEND = 2

        @overload(WEEKDAY)
        def overtime_pay(self, hours, pay_rate):
            return 0 if hours <= 8 else (hours - 8) * pay_rate / 2

        @overload(WEEKEND)
        def overtime_pay(self, hours, pay_rate):
            return hours * pay_rate / 2

        def pay(self, hours_worked, pay_rate):
            base_pay = hours_worked * pay_rate
            overtime_pay = self.overtime_pay(hours_worked, pay_rate)
            return base_pay + overtime_pay

    MONDAY = PayType.WEEKDAY
    TUESDAY = PayType.WEEKDAY
    WEDNESDAY = PayType.WEEKDAY
    THURSDAY = PayType.WEEKDAY
    FRIDAY = PayType.WEEKDAY
    SATURDAY = PayType.WEEKEND
    SUNDAY = PayType.WEEKEND

    def pay(self, hours_worked, pay_rate):
        return self.value.pay(hours_worked, pay_rate)

Java est utile comme pratique, mais Python se sent exagéré (subjectif). J'ai essayé de le mettre en œuvre, mais je pense que c'est un exemple subtil qui ne peut être qualifié de bon ou mauvais.

Veuillez voir dans la mesure où vous pouvez faire quelque chose comme ça.

3.4


>>> from strategy_enum_pattern import PayrollDay
>>> PayrollDay.MONDAY.pay(10, 1000.0)
11000.0
>>> PayrollDay.WEDNESDAY.pay(8, 1000.0)
8000.0
>>> PayrollDay.SATURDAY.pay(10, 1000.0)
15000.0
>>> PayrollDay.SUNDAY.pay(8, 1000.0)
12000.0

Membre d'énumération implicite

Étant donné que les membres du type d'énumération définissent des variables de classe, le type d'énumération suivant ne peut pas être défini.

3.4


class Color(Enum):
    red, green, blue

Quand je l'exécute, j'obtiens * NameError *.

3.4


NameError: name 'red' is not defined

Ajout du module enum [PEP 435 -Ne pas avoir à spécifier de valeurs pour enums-](https://www.python.org/dev/peps/pep-0435/#not-having-to-specify-values-for- selon les énumérations)

Cons: involves much magic in the implementation, which makes even the definition of such enums baffling when first seen. Besides, explicit is better than implicit.

Les raisons pour lesquelles il n'est pas possible de définir une valeur implicite sont que l'implémentation est magique, déroutante à première vue, et plus contraire au Zen explicite qu'implicite.

C'est correct pour la culture Python, donc ça va, mais la note de Nick Coghlan Prise en charge des syntaxes de déclaration alternatives enum_creation.html # support-for-another-declaration-syntaxes) mentionne comment implémenter des membres d'énumération implicites.

Implicit enums that don’t really support normal code execution in the class body, and allow the above to be simplified further. It’s another variant of the autonumbered example in the test suite, but one that diverges substantially from normal Python semantics: merely mentioning a name will create a new reference to that name. While there are a number of ways to get into trouble when doing this, the basic concept would be to modify __prepare__ on the metaclass to return a namespace that implements __missing__ as returning a custom sentinel value and overrides __getitem__ to treat repeating a name as an error:

Python ~~ Black Magic ~~ C'est une méthode intéressante pour la métaprogrammation, donc je l'ai implémentée.

La méthode spéciale __prepare__ a été ajoutée à partir de Python 3 et la métaclasse est initialisée. Accroche le processus de retour de l'espace de noms de cette classe lorsque __missing__ accroche la gestion des erreurs KeyError pour les sous-classes du dictionnaire lorsque la clé n'existe pas dans le dictionnaire Faire.

Vous pouvez accrocher la définition de l'espace de noms d'une classe en renvoyant un dictionnaire avec «missing» comme objet d'espace de noms dans le précédent «prepare».

Je ne sais pas de quoi vous parlez, alors codons-le.

prepare_missing.py


class MyNameSpace(dict):
    def __missing__(self, key):
        self[key] = value = 'x'
        return value

class MyMetaclass(type):
    @classmethod
    def __prepare__(metacls, cls, bases):
        return MyNameSpace()

    def __new__(metacls, cls, bases, classdict):
        print('classdict is', classdict.__class__)
        return super().__new__(metacls, cls, bases, classdict)

Utilisez cette métaclasse pour définir une classe.

3.4


>>> from prepare_missing import MyMetaclass
>>> class Color(metaclass=MyMetaclass):
...     red, green, blue
... 
classdict is <class 'status.MyNameSpace'>
>>> Color.red
'x'
>>> Color.blue
'x'

Lors de la définition d'une classe, la méthode __new__ de la métaclasse * MyMetaclass * est appelée et * MyNameSpace * est passé à l'espace de noms de classe (* classdict *). Le * MyNameSpace * __missing__ définit la chaîne * x * pour les noms qui n'ont pas de clé, c'est-à-dire qui n'existent pas dans l'espace de noms de classe.

3.4


>>> from extenum import ImplicitEnum
>>> class Color(ImplicitEnum):
...     RED
...     GREEN
...     BLUE
...
>>> for name, const in Color.__members__.items():
...     print(name, ':', const.value)
...
RED : 1
GREEN : 2
BLUE : 3

En passant, le décorateur * @ overload * de * ConstantSpecificEnum * dans la section précédente est également implémenté en utilisant ce mécanisme. Je pense qu'il y a des avantages et des inconvénients à masquer cette définition de décorateur elle-même, mais même si elle est cachée, cela semble étrange du côté de l'utilisateur afin que * @ classmethod * et * @ staticmethod * puissent être gérés globalement. Je me suis demandé s'il y en avait un.

EnumSet EnumSet est comme une classe utilitaire qui produit un ensemble contenant des constantes Enum. Je l'ai implémenté en regardant [EnumSet] de Java (http://docs.oracle.com/javase/jp/8/docs/api/java/util/EnumSet.html).

À des fins faciles à comprendre, l'utilisation d'EnumSet pour implémenter des traitements tels que des opérations d'indicateur, qui a été implémentée par arithmétique binaire, crée une atmosphère facile pour les humains.

3.4


>>> from enum import Enum
>>> class Mode(Enum):
...     READ = 4
...     WRITE = 2
...     EXECUTE = 1
... 

Essayons d'utiliser ce type d'énumération * Mode *.

3.4


>>> from extenum import EnumSet

>>> EnumSet.all_of(Mode)  #Générer un EnumSet contenant tous les membres énumérés en mode
EnumSet({<Mode.READ: 4>, <Mode.WRITE: 2>, <Mode.EXECUTE: 1>})

>>> EnumSet.none_of(Mode)  #Générer un EnumSet vide qui accepte un membre énuméré de Mode
EnumSet()

>>> enumset = EnumSet.of(Mode.READ, Mode.EXECUTE)  #Générer un EnumSet contenant n'importe quel membre d'énumération
>>> enumset
EnumSet({<Mode.READ: 4>, <Mode.EXECUTE: 1>})
>>> enumset.update(EnumSet.of(Mode.READ, Mode.WRITE))
>>> enumset
EnumSet({<Mode.READ: 4>, <Mode.WRITE: 2>, <Mode.EXECUTE: 1>})
>>> Mode.WRITE in enumset
True
>>> 2 in enumset
False

Les rassembler dans un EnumSet facilite la gestion des modes et des options.

Résumé

Les fonctionnalités suivantes sont fournies par extenum en tant qu'extension enum de type Java.

Il hérite et étend les standards * Enum *, * EnumMeta * et * _EnumDict *, donc il fonctionne probablement comme un type d'énumération standard.

Veuillez nous faire savoir s'il existe d'autres pratiques d'énumération qui pourraient être intéressantes. Parfois, le but est de le fabriquer plutôt que de l'utiliser.

Recommended Posts

J'ai créé un package extenum qui étend enum
J'ai créé un installateur Ansible
J'ai créé une application Android qui affiche Google Map
J'ai créé un serveur Xubuntu.
J'ai créé un modèle de détection d'anomalies qui fonctionne sur iOS
J'ai fait un peintre discriminateur Anpanman
J'ai fait un kit de démarrage angulaire
J'ai fait un package qui peut comparer des analyseurs morphologiques avec Python
J'ai créé une IA qui recadre joliment une image en utilisant Saliency Map
J'ai fait quelque chose qui bouge (gamme plus large)
J'ai créé une application d'analyse de fréquence en ligne
J'ai créé un module alternatif pour les japandas.
J'ai créé une extension Chrome qui affiche un graphique sur la page Amedas
J'ai créé une IA qui prédit des anecdotes et m'a fait déduire mes anecdotes. Hee-AI
Avec LINEBot, j'ai fait une application qui m'informe de "l'heure du bus"
J'ai créé un package pour créer un fichier exécutable à partir du code source Hy
Obstacle (noir) J'ai fait une chenille d'évitement automatique.
J'ai créé un bouton Amazon Web Service Dash
J'ai eu une erreur indiquant que Python n'a pas pu lire settings.ini
J'ai créé une VM qui exécute OpenCV pour Python
J'ai créé une application qui m'avertit si je joue avec mon smartphone pendant mes études avec OpenCV
J'ai fait un bouton IFTTT qui déverrouille l'entrée 2 lock sésame avec 1 bouton (via AWS Lambda)
J'ai créé une API avec Docker qui renvoie la valeur prédite du modèle d'apprentissage automatique