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 ?
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 ?
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 ).
enum34
, faire $ pip install enum34
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
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é.
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.
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)
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
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.
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.
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.
@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.
@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 :)
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)
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).
@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.
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.
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.
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.