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 is red, the value of Color.blue is 3, 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 :

  1. Pour hériter de Enum, les types de mélange doivent être placés avant la classe Enum elle-même dans la liste des classes de base, comme dans l’exemple de IntEnum ci-dessus.
  2. 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, tels int ou str.
  3. 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.
  4. Formatage de style % : %s et %r appellent respectivement les méthodes __str__() et __repr__() de la classe Enum ; 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.
  5. str.format() (or format()) will use the mixed-in type’s __format__(). If the Enum class’s str() or repr() 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

Note

La méthode __new__(), si définie, est appelée à la création des membres de l’énumération ; elle est ensuite remplacée par la méthode __new__() de Enum, qui est utilisée après la création de la classe pour la recherche des membres existants.

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.