8.13. enum
— Énumerations¶
Nouveau dans la version 3.4.
Code source : Lib/enum.py
Une énumération est un ensemble de noms symboliques, appelés membres, liés à des valeurs constantes et uniques. Au sein d’une énumération, les membres peuvent être comparés entre eux et il est possible d’itérer sur l’énumération elle-même.
8.13.1. Contenu du module¶
This module defines two enumeration classes that can be used to define unique
sets of names and values: Enum
and IntEnum
. It also defines
one decorator, unique()
.
-
class
enum.
Enum
¶ Classe de base pour créer une énumération de constantes. Voir la section API par fonction pour une syntaxe alternative de construction.
-
class
enum.
IntEnum
¶ Classe de base pour créer une énumération de constantes qui sont également des sous-classes de
int
.
-
enum.
unique
()¶ Décorateur de classe qui garantit qu’une valeur ne puisse être associée qu’à un seul nom.
8.13.2. Création d’une Enum¶
Une énumération est créée comme une class
, ce qui la rend facile à lire et à écrire. Une autre méthode de création est décrite dans API par fonction. Pour définir une énumération, il faut hériter de Enum
de la manière suivante :
>>> from enum import Enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
...
Note
Nomenclature
- La classe
Color
est une énumération (ou un enum). - The attributes
Color.red
,Color.green
, etc., are enumeration members (or enum members). - The enum members have names and values (the name of
Color.red
isred
, the value ofColor.blue
is3
, etc.)
Note
Même si on utilise la syntaxe en class
pour créer des énumérations, les Enums ne sont pas des vraies classes Python. Voir En quoi les Enums sont différentes ? pour plus de détails.
Les membres d’une énumération ont une représentation en chaîne de caractères compréhensible par un humain :
>>> print(Color.red)
Color.red
… tandis que leur repr
contient plus d’informations :
>>> print(repr(Color.red))
<Color.red: 1>
Le type d’un membre est l’énumération auquel ce membre appartient :
>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>
Les membres ont également un attribut qui contient leur nom :
>>> print(Color.red.name)
red
Les énumérations sont itérables, l’ordre d’itération est celui dans lequel les membres sont déclarés :
>>> class Shake(Enum):
... vanilla = 7
... chocolate = 4
... cookies = 9
... mint = 3
...
>>> for shake in Shake:
... print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint
Les membres d’une énumération sont hachables, ils peuvent ainsi être utilisés dans des dictionnaires ou des ensembles :
>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'}
True
8.13.3. Accès dynamique aux membres et à leurs attributs¶
Sometimes it’s useful to access members in enumerations programmatically (i.e.
situations where Color.red
won’t do because the exact color is not known
at program-writing time). Enum
allows such access:
>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>
Pour accéder aux membres par leur nom, utilisez l’accès par indexation :
>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>
Pour obtenir l’attribut name
ou value
d’un membre :
>>> member = Color.red
>>> member.name
'red'
>>> member.value
1
8.13.4. Duplication de membres et de valeurs¶
Il n’est pas possible d’avoir deux membres du même nom dans un enum :
>>> class Shape(Enum):
... square = 2
... square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'square'
Cependant deux membres peuvent avoir la même valeur. Si deux membres A et B ont la même valeur (et que A est défini en premier), B sera un alias de A. Un accès par valeur avec la valeur commune à A et B renverra A. Un accès à B par nom renverra aussi A :
>>> class Shape(Enum):
... square = 2
... diamond = 1
... circle = 3
... alias_for_square = 2
...
>>> Shape.square
<Shape.square: 2>
>>> Shape.alias_for_square
<Shape.square: 2>
>>> Shape(2)
<Shape.square: 2>
Note
Il est interdit de créer un membre avec le même nom qu’un attribut déjà défini (un autre membre, une méthode, etc.) ou de créer un attribut avec le nom d’un membre.
8.13.5. Coercition d’unicité des valeurs d’une énumération¶
Par défaut, les énumérations autorisent les alias de nom pour une même valeur. Quand ce comportement n’est pas désiré, il faut utiliser le décorateur suivant pour s’assurer que chaque valeur n’est utilisée qu’une seule fois au sein de l’énumération :
-
@
enum.
unique
Un décorateur de class
spécifique aux énumérations. Il examine l’attribut __members__
d’une énumération et recherche des alias ; s’il en trouve, l’exception ValueError
est levée avec des détails :
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... one = 1
... two = 2
... three = 3
... four = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
8.13.6. Itération¶
Itérer sur les membres d’une énumération ne parcourt pas les alias :
>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]
The special attribute __members__
is an ordered dictionary mapping names
to members. It includes all names defined in the enumeration, including the
aliases:
>>> for name, member in Shape.__members__.items():
... name, member
...
('square', <Shape.square: 2>)
('diamond', <Shape.diamond: 1>)
('circle', <Shape.circle: 3>)
('alias_for_square', <Shape.square: 2>)
L’attribut __members__
peut servir à accéder dynamiquement aux membres de l’énumération. Par exemple, pour trouver tous les alias :
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['alias_for_square']
8.13.7. Comparaisons¶
Les membres d’une énumération sont comparés par identité :
>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True
Les comparaisons d’ordre entre les valeurs d’une énumération n’existent pas ; les membres d’un enum ne sont pas des entiers (voir cependant IntEnum ci-dessous) :
>>> Color.red < Color.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Color() < Color()
A contrario, les comparaisons d’égalité existent :
>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True
Les comparaisons avec des valeurs ne provenant pas d’énumérations sont toujours fausses (ici encore, IntEnum
a été conçue pour fonctionner différemment, voir ci-dessous) :
>>> Color.blue == 2
False
8.13.8. Membres et attributs autorisés dans une énumération¶
Les exemples précédents utilisent des entiers pour énumérer les valeurs. C’est un choix concis et pratique (et implémenté par défaut dans l”API par fonction), mais ce n’est pas une obligation. Dans la majorité des cas, il importe peu de connaître la valeur réelle d’une énumération. Il est toutefois possible de donner une valeur arbitraire aux énumérations, si cette valeur est vraiment significative.
Les énumérations sont des classes Python et peuvent donc avoir des méthodes et des méthodes spéciales. L’énumération suivante :
>>> class Mood(Enum):
... funky = 1
... happy = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.happy
...
amène :
>>> Mood.favorite_mood()
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> str(Mood.funky)
'my custom str! 1'
The rules for what is allowed are as follows: names that start and end with
a single underscore are reserved by enum and cannot be used; all other
attributes defined within an enumeration will become members of this
enumeration, with the exception of special methods (__str__()
,
__add__()
, etc.) and descriptors (methods are also descriptors).
Remarque : si l’énumération définit __new__()
ou __init__()
, alors la (ou les) valeur affectée au membre sera passée à ces méthodes. Voir l’exemple de Planet.
8.13.9. Restricted subclassing of enumerations¶
Subclassing an enumeration is allowed only if the enumeration does not define any members. So this is forbidden:
>>> class MoreColor(Color):
... pink = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations
Mais celui-ci est correct :
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... happy = 1
... sad = 2
...
Autoriser l’héritage d”enums définissant des membres violerait des invariants sur les types et les instances. D’un autre côté, il est logique d’autoriser un groupe d’énumérations à partager un comportement commun (voir par exemple OrderedEnum).
8.13.10. Sérialisation¶
Les énumérations peuvent être sérialisées et dé-sérialisées :
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True
Les restrictions habituelles de sérialisation s’appliquent : les enums à sérialiser doivent être déclarés dans l’espace de nom de haut niveau du module car la dé-sérialisation nécessite que ces enums puissent être importés depuis ce module.
Note
Depuis la version 4 du protocole de pickle, il est possible de sérialiser facilement des enums imbriqués dans d’autres classes.
Redéfinir la méthode __reduce_ex__()
permet de modifier la sérialisation ou la dé-sérialisation des membres d’une énumération.
8.13.11. API par fonction¶
La Enum
est appelable et implémente l’API par fonction suivante :
>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<enum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]
La sémantique de cette API est similaire à namedtuple
. Le premier argument de l’appel à Enum
est le nom de l’énumération.
Le second argument est la source des noms des membres de l’énumération. Il peut être une chaîne de caractères contenant les noms séparés par des espaces, une séquence de noms, une séquence de couples clé / valeur ou un dictionnaire (p. ex. un dict) de valeurs indexées par des noms. Les deux dernières options permettent d’affecter des valeurs arbitraires aux énumérations ; les autres affectent automatiquement des entiers en commençant par 1 (le paramètre start
permet de changer la valeur de départ). Ceci renvoie une nouvelle classe dérivée de Enum
. En d’autres termes, la déclaration de Animal
ci-dessus équivaut à :
>>> class Animal(Enum):
... ant = 1
... bee = 2
... cat = 3
... dog = 4
...
La valeur de départ par défaut est 1
et non 0
car 0
au sens booléen vaut False
alors que tous les membres d’une enum valent True
.
La sérialisation d’énumérations créées avec l’API en fonction peut être source de problèmes, car celle-ci repose sur des détails d’implémentation de l’affichage de la pile d’appel pour tenter de déterminer dans quel module l’énumération est créée (p. ex. elle échouera avec les fonctions utilitaires provenant d’un module séparé et peut ne pas fonctionner avec IronPython ou Jython). La solution consiste à préciser explicitement le nom du module comme ceci :
>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)
Avertissement
Si module
n’est pas fourni et que Enum ne peut pas le deviner, les nouveaux membres de l’Enum ne seront pas dé-sérialisables ; pour garder les erreurs au plus près de leur origine, la sérialisation sera désactivée.
Le nouveau protocole version 4 de pickle se base lui aussi, dans certains cas, sur le fait que __qualname__
pointe sur l’endroit où pickle peut trouver la classe. Par exemple, si la classe était disponible depuis la classe SomeData dans l’espace de nom de plus haut niveau :
>>> Animal = Enum('Animal', 'ant bee cat dog', qualname='SomeData.Animal')
La signature complète est la suivante :
Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
value: | Le nom de la la nouvelle classe Enum. |
---|---|
names: | Les membres de l’énumération. Une chaîne de caractères séparés par des espaces ou des virgules (la valeur de départ est fixée à 1, sauf si spécifiée autrement) : 'red green blue' | 'red,green,blue' | 'red, green, blue'
ou un itérateur sur les noms : ['red', 'green', 'blue']
ou un itérateur sur les tuples (nom, valeur) : [('cyan', 4), ('magenta', 5), ('yellow', 6)]
ou une correspondance : {'chartreuse': 7, 'sea_green': 11, 'rosemary': 42}
|
module: | nom du module dans lequel la classe Enum se trouve. |
qualname: | localisation de la nouvelle classe Enum dans le module. |
type: | le type à mélanger dans la nouvelle classe Enum. |
start: | index de départ si uniquement des noms sont passés. |
Modifié dans la version 3.5: Ajout du paramètre start.
8.13.12. Énumérations dérivées¶
8.13.12.1. IntEnum¶
A variation of Enum
is provided which is also a subclass of
int
. Members of an IntEnum
can be compared to integers;
by extension, integer enumerations of different types can also be compared
to each other:
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Request(IntEnum):
... post = 1
... get = 2
...
>>> Shape == 1
False
>>> Shape.circle == 1
True
>>> Shape.circle == Request.post
True
Elles ne peuvent cependant toujours pas être comparées à des énumérations standards de Enum
:
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Color(Enum):
... red = 1
... green = 2
...
>>> Shape.circle == Color.red
False
Les valeurs de IntEnum
se comportent comme des entiers, comme on pouvait s’y attendre :
>>> int(Shape.circle)
1
>>> ['a', 'b', 'c'][Shape.circle]
'b'
>>> [i for i in range(Shape.square)]
[0, 1]
For the vast majority of code, Enum
is strongly recommended,
since IntEnum
breaks some semantic promises of an enumeration (by
being comparable to integers, and thus by transitivity to other
unrelated enumerations). It should be used only in special cases where
there’s no other choice; for example, when integer constants are
replaced with enumerations and backwards compatibility is required with code
that still expects integers.
8.13.12.2. Autres¶
Bien que IntEnum
fasse partie du module enum
, elle serait très simple à implémenter hors de ce module :
class IntEnum(int, Enum):
pass
Ceci montre comment définir des énumérations dérivées similaires ; par exemple une classe StrEnum
qui dériverait de str
au lieu de int
.
Quelques règles :
- Pour hériter de
Enum
, les types de mélange doivent être placés avant la classeEnum
elle-même dans la liste des classes de base, comme dans l’exemple deIntEnum
ci-dessus. - Même si une classe
Enum
peut avoir des membres de n’importe quel type, dès lors qu’un type de mélange est ajouté, tous les membres doivent être de ce type, p. ex.int
ci-dessus. Cette restriction ne s’applique pas aux types de mélange qui ne font qu’ajouter des méthodes et ne définissent pas de type de données, telsint
oustr
. - Quand un autre type de données est mélangé, l’attribut
value
n’est pas identique au membre de l’énumération lui-même, bien qu’ils soient équivalents et égaux en comparaison. - Formatage de style % : %s et %r appellent respectivement les méthodes
__str__()
et__repr__()
de la classeEnum
; les autres codes, comme %i ou %h pour IntEnum, s’appliquent au membre comme si celui-ci était converti en son type de mélange. str.format()
(orformat()
) will use the mixed-in type’s__format__()
. If theEnum
class’sstr()
orrepr()
is desired, use the !s or !r format codes.
8.13.13. Exemples intéressants¶
While Enum
and IntEnum
are expected to cover the majority of
use-cases, they cannot cover them all. Here are recipes for some different
types of enumerations that can be used directly, or as examples for creating
one’s own.
8.13.13.1. AutoNumber¶
Avoids having to specify the value for each enumeration member:
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... red = ()
... green = ()
... blue = ()
...
>>> Color.green.value == 2
True
8.13.13.2. OrderedEnum¶
Une énumération ordonnée qui n’est pas basée sur IntEnum
et qui, par conséquent, respecte les invariants classiques de Enum
(comme par exemple l’impossibilité de pouvoir être comparée à d’autres énumérations)
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
8.13.13.3. DuplicateFreeEnum¶
Lève une erreur si un membre est dupliqué, plutôt que de créer un alias :
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... red = 1
... green = 2
... blue = 3
... grene = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'grene' --> 'green'
Note
Cet exemple d’héritage de Enum est intéressant pour ajouter ou modifier des comportements comme interdire les alias. Si vous ne souhaitez qu’interdire les alias, il suffit d’utiliser le décorateur unique()
.
8.13.13.4. Planet¶
Si __new__()
ou __init__()
sont définies, la valeur du membre de l’énumération sera passée à ces méthodes :
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
8.13.14. En quoi les Enums sont différentes ?¶
Les enums ont une métaclasse spéciale qui affecte de nombreux aspects des classes dérivées de Enum et de leur instances (membres).
8.13.14.1. Classes Enum¶
The EnumMeta
metaclass is responsible for providing the
__contains__()
, __dir__()
, __iter__()
and other methods that
allow one to do things with an Enum
class that fail on a typical
class, such as list(Color) or some_var in Color. EnumMeta
is
responsible for ensuring that various other methods on the final Enum
class are correct (such as __new__()
, __getnewargs__()
,
__str__()
and __repr__()
).
8.13.14.2. Membres d’Enum (c.-à-d. instances)¶
Il est intéressant de souligner que les membres d’une Enum sont des singletons. La classe EnumMeta
les crée tous au moment de la création de la classe Enum
elle-même et implémente une méthode __new__()
spécifique. Cette méthode renvoie toujours les instances de membres déjà existantes pour être sûr de ne jamais en instancier de nouvelles.
8.13.14.3. Aspects approfondis¶
Enum
members are instances of an Enum
class, and even
though they are accessible as EnumClass.member, they should not be accessed
directly from the member as that lookup may fail or, worse, return something
besides the Enum
member you looking for:
>>> class FieldTypes(Enum):
... name = 0
... value = 1
... size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2
Modifié dans la version 3.5.
The __members__
attribute is only available on the class.
Si votre classe Enum
contient des méthodes supplémentaires, comme la classe Planet ci-dessus, elles s’afficheront avec un appel à dir()
sur le membre, mais pas avec un appel sur la classe :
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
The __new__()
method will only be used for the creation of the
Enum
members – after that it is replaced. Any custom __new__()
method must create the object and set the _value_
attribute
appropriately.
If you wish to change how Enum
members are looked up you should either
write a helper function or a classmethod()
for the Enum
subclass.