1141 votes

Comment puis-je représenter un 'Enum' en Python ?

Je suis principalement un développeur C#, mais je travaille actuellement sur un projet en Python.

Comment puis-je représenter l'équivalent d'un Enum en Python ?

2986voto

Alec Thomas Points 5815

Enums ont été ajoutés à Python 3.4, comme décrit dans le document suivant PEP 435 . Il a également été reporté en arrière vers 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, et 2.4 sur pypi.

Pour des techniques Enum plus avancées, essayez le bibliothèque aenum (2.7, 3.3+, même auteur que enum34 . Le code n'est pas parfaitement compatible entre py2 et py3, par exemple vous aurez besoin de __order__ dans python 2 ).

  • Pour utiliser enum34 , faire $ pip install enum34
  • Pour utiliser aenum , faire $ pip install aenum

Installation de enum (sans chiffres) installera une version complètement différente et incompatible.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

ou de manière équivalente :

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

Dans les versions antérieures, une façon d'accomplir les enums est :

def enum(**enums):
    return type('Enum', (), enums)

qui est utilisé comme suit :

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

Vous pouvez aussi facilement prendre en charge l'énumération automatique avec quelque chose comme ceci :

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

et utilisé comme tel :

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

La prise en charge de la reconversion des valeurs en noms peut être ajoutée de cette manière :

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

Cela écrase tout ce qui porte ce nom, mais c'est utile pour rendre vos enums en sortie. Il lancera un KeyError si le mappage inverse n'existe pas. Avec le premier exemple :

>>> Numbers.reverse_mapping['three']
'THREE'

Si vous utilisez MyPy, une autre façon d'exprimer les "enums" est avec typing.Literal .

Par exemple :

from typing import Literal #python >=3.8
from typing_extensions import Literal #python 2.7, 3.4-3.7

Animal = Literal['ant', 'bee', 'cat', 'dog']

def hello_animal(animal: Animal):
    print(f"hello {animal}")

hello_animal('rock') # error
hello_animal('bee') # passes

2 votes

Je n'ai pas été capable de comprendre, pourquoi ils ont passé kwargs(**named) dans la méthode enum(*sequential, **named) ? Veuillez expliquer. Sans kwargs, cela fonctionnera aussi. Je l'ai vérifié.

1 votes

Il serait bon de mettre à jour la fonction Python 2 pour qu'elle soit compatible avec l'API fonctionnelle Enum(name, values) de Python 3.

0 votes

Les var kwargs ( **named ) dans la fonction enum pour les anciennes versions est de supporter les valeurs personnalisées : enum("blue", "red", "green", black=0)

975voto

Alexandru Nedelcu Points 3801

Avant PEP 435, Python n'avait pas d'équivalent mais vous pouviez implémenter le vôtre.

Personnellement, j'aime garder les choses simples (j'ai vu des exemples horriblement complexes sur le net), quelque chose comme ceci ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Dans Python 3.4 ( PEP 435 ), vous pouvez faire Enum la classe de base. Cela vous permet d'obtenir un peu de fonctionnalité supplémentaire, décrite dans le PEP. Par exemple, les membres d'un enum sont distincts des entiers, et ils sont composés d'un élément name et un value .

from enum import Enum

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

Si vous ne voulez pas taper les valeurs, utilisez le raccourci suivant :

class Animal(Enum):
    DOG, CAT = range(2)

Enum mises en œuvre peuvent être convertis en listes et sont itérables . L'ordre de ses membres est l'ordre de déclaration et n'a rien à voir avec leurs valeurs. Par exemple :

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

3 votes

Quel est l'intérêt de définir des valeurs numériques (1 et 2) ? Elles semblent inutiles, et c'est pourquoi je préfère la solution de zacherates.

2 votes

L'exemple montre clairement que des constantes sont définies. Il y a cependant un problème avec la sécurité des types, qui peut causer des problèmes.

0 votes

Vous devriez d'abord avoir créé une instance de la classe Animal . X = Animal() z = X.DOG

356voto

shahjapan Points 4043

Voici une mise en œuvre :

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Voici son utilisation :

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)

56 votes

Excellent. Il est possible de l'améliorer encore en remplaçant __setattr__(self, name, value) et peut-être __delattr__(self, name) de sorte que si vous écrivez accidentellement Animals.DOG = CAT il ne réussira pas silencieusement.

17 votes

@shahjapan : Intéressant, mais relativement lent : un test est effectué pour chaque accès du genre Animals.DOG ; de plus, les valeurs des constats sont des chaînes de caractères, de sorte que les comparaisons avec ces constats sont plus lentes que si, disons, des entiers étaient autorisés comme valeurs.

3 votes

@shahjapan : Je dirais que cette solution n'est pas aussi lisible que les solutions plus courtes d'Alexandru ou de Mark, par exemple. Mais c'est une solution intéressante :)

224voto

Mark Harrison Points 77152

Si vous avez besoin des valeurs numériques, voici le moyen le plus rapide :

dog, cat, rabbit = range(3)

Dans Python 3.x, vous pouvez également ajouter un placeholder étoilé à la fin, qui absorbera toutes les valeurs restantes de l'intervalle au cas où vous ne vous souciez pas de gaspiller de la mémoire et ne savez pas compter :

dog, cat, rabbit, horse, *_ = range(100)

3 votes

Mais cela peut demander plus de mémoire !

0 votes

Je ne vois pas l'intérêt du placeholder étoilé étant donné que Python vérifiera le nombre de valeurs à décompresser (il fera donc le comptage pour vous).

1 votes

@GabrielDevillers, je pense que Python lèvera une exception s'il y a un décalage sur le nombre d'éléments dans le tuple à assigner.

142voto

Ashwin Points 17537

La meilleure solution pour vous dépendra de ce que vous attendez de votre faux enum .

Enum simple :

Si vous avez besoin de la enum comme une simple liste de noms identifier les différents articles la solution par Mark Harrison (ci-dessus) est génial :

Pen, Pencil, Eraser = range(0, 3)

Utilisation d'un range vous permet également de définir n'importe quel valeur de départ :

Pen, Pencil, Eraser = range(9, 12)

En plus de ce qui précède, si vous exigez également que les articles appartiennent à un conteneur en quelque sorte, puis les intégrer dans une classe :

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Pour utiliser l'item enum, vous devez maintenant utiliser le nom du conteneur et le nom de l'item :

stype = Stationery.Pen

Enum complexe :

Pour de longues listes d'enum ou des utilisations plus compliquées d'enum, ces solutions ne suffiront pas. Vous pouvez vous tourner vers la recette de Will Ware pour Simuler des énumérations en Python publié dans le Livre de recettes Python . Une version en ligne de ce document est disponible aquí .

Plus d'informations :

PEP 354 : Enumérations en Python présente les détails intéressants d'une proposition d'enum dans Python et les raisons pour lesquelles elle a été rejetée.

9 votes

Avec range vous pouvez omettre le premier argument s'il est égal à 0

0 votes

Un autre faux enum qui convient à certains usages est my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))) . Ensuite, my_enum peut être utilisé dans la recherche, par exemple, my_enum['Item0'] peut être un index dans une séquence. Vous pourriez vouloir envelopper le résultat de str.split dans une fonction qui lève une exception s'il y a des doublons.

0 votes

Joli ! Pour les drapeaux, vous pouvez Flag1, Flag2, Flag3 = [2**i for i in range(3)]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X