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.

Note

Convention de nommage pour les membres d'une Enum

Puisque les Enums sont utilisées pour représenter des constantes, il est recommandé d'utiliser des majuscules (format MAJUSCULE_AVEC_SOULIGNÉS) pour leurs membres. Cette convention de style sera utilisée dans les exemples.

Contenu du module

Ce module définit quatre classes d'énumération qui permettent de définir des ensembles uniques de noms et de valeurs : Enum, IntEnum, Flag et IntFlag. Il fournit également un décorateur, unique(), ainsi qu'une classe utilitaire, auto.

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.

class enum.IntFlag

Classe de base pour créer une énumération de constantes pouvant être combinées avec des opérateurs de comparaison bit-à-bit, sans perdre leur qualité de IntFlag. Les membres de IntFlag sont aussi des sous-classes de int.

class enum.Flag

Classe de base pour créer une énumération de constantes pouvant être combinées avec des opérateurs de comparaison bit-à-bit, sans perdre leur qualité de Flag.

enum.unique()

Décorateur de classe qui garantit qu'une valeur ne puisse être associée qu'à un seul nom.

class enum.auto

Instances are replaced with an appropriate value for Enum members. Initial value starts at 1.

Nouveau dans la version 3.6: Flag, IntFlag, auto

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

Valeurs des membres d'une Enum

La valeur d'un membre peut être de n'importe quel type : int, str, etc. Si la valeur exacte n'a pas d'importance, utilisez des instances de auto et une valeur appropriée sera choisie pour vous. Soyez vigilant si vous mélangez auto avec d'autres valeurs.

Note

Nomenclature

  • La classe Color est une énumération (ou un enum).

  • Les attributs Color.RED, Color.GREEN, etc., sont les membres de l'énumération (ou les membres de l'enum) et sont fonctionnellement des constantes.

  • Les membres de l'enum ont chacun un nom et une valeur ; le nom de Color.RED est RED, la valeur de Color.BLUE est 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

Accès dynamique aux membres et à leurs attributs

Il est parfois utile de pouvoir accéder dynamiquement aux membres d'une énumération (p. ex. dans des situations où il ne suffit pas d'utiliser Color.RED car la couleur précise n'est pas connue à l'écriture du programme). Enum permet de tels accès :

>>> 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

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.

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

Valeurs automatiques

Si la valeur exacte n'a pas d'importance, vous pouvez utiliser auto :

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Les valeurs sont déterminées par _generate_next_value_(), qui peut être redéfinie :

>>> class AutoName(Enum):
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

Note

La méthode _generate_next_value_() doit renvoyer le prochain int de la séquence à partir du dernier int fourni, mais l'implémentation de cette fonction peut changer.

Note

La méthode _generate_next_value_() doit être définie avant tout membre.

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>]

L'attribut spécial __members__ est un dictionnaire en lecture seule ordonné qui fait correspondre les noms aux membres. Il inclut tous les noms définis dans l'énumération, alias compris :

>>> 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']

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: '<' not supported between instances of 'Color' and '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

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'

Les règles pour ce qui est autorisé sont les suivantes : les noms qui commencent et finissent avec un seul tiret bas sont réservés par enum et ne peuvent pas être utilisés ; tous les autres attributs définis dans l'énumération en deviendront des membres, à l'exception des méthodes spéciales (__str__(), __add__(), etc.), des descripteurs (les méthodes sont aussi des descripteurs) et des noms de variable listés dans _ignore_.

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.

Restrictions sur l'héritage

Une nouvelle classe Enum doit avoir une classe Enum de base, au plus un type de données concret et autant de classes de mélange (basées sur object) que nécessaire. L'ordre de ces classes de base est le suivant :

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Hériter d'une énumération n'est permis que si cette énumération ne définit aucun membre. Le code suivant n'est pas autorisé :

>>> 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).

Sérialisation

Les énumérations peuvent être sérialisées et déserialisé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éserialisation 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.

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éserialisables ; 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.

Énumérations dérivées

IntEnum

La première version dérivée de Enum qui existe est aussi une sous-classe de int. Les membres de IntEnum peuvent être comparés à des entiers et, par extension, les comparaisons entre des énumérations entières de type différent sont possibles :

>>> 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]

IntFlag

La version dérivée suivante de Enum est IntFlag. Elle est aussi basée sur int, à la différence près que les membres de IntFlag peuvent être combinés en utilisant les opérateurs bit-à-bit (&, |, ^, ~) et que le résultat reste un membre de IntFlag. Cependant, comme le nom l'indique, les membres d'une classe IntFlag héritent aussi de int et peuvent être utilisés là où un int est utilisé. Toute opération sur un membre d'une classe IntFlag, autre qu'un opérateur bit-à-bit lui fait perdre sa qualité de IntFlag.

Nouveau dans la version 3.6.

Exemple d'une classe IntFlag :

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

Il est aussi possible de nommer les combinaisons :

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm.-8: -8>

Une autre différence importante entre IntFlag et Enum est que, si aucune option n'est activée (la valeur vaut 0), son évaluation booléenne est False :

>>> Perm.R & Perm.X
<Perm.0: 0>
>>> bool(Perm.R & Perm.X)
False

Comme les membres d'une classe IntFlag héritent aussi de int, ils peuvent être combinés avec eux :

>>> Perm.X | 8
<Perm.8|X: 9>

Option

La dernière version dérivée est la classe Flag. Comme IntFlag, les membres d'une classe Flag peuvent être combinés en utilisant les opérateurs de comparaison bit-à-bit. Cependant, à la différence de IntFlag, ils ne peuvent ni être combinés, ni être comparés avec une autre énumération Flag, ni avec int. Bien qu'il soit possible de définir directement les valeurs, il est recommandé d'utiliser auto comme valeur et de laisser Flag choisir une valeur appropriée.

Nouveau dans la version 3.6.

Comme avec IntFlag, si une combinaison de membres d'une classe Flag n'active aucune option, l'évaluation booléenne de la comparaison est False :

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color.0: 0>
>>> bool(Color.RED & Color.GREEN)
False

Les options de base doivent avoir des puissances de deux pour valeurs (1, 2, 4, 8, ...) mais pas les combinaisons :

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Donner un nom à la valeur « aucune option activée » ne change pas sa valeur booléenne :

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Note

Dans la plupart des cas, il est fortement recommandé d'utiliser Enum et Flag pour écrire du code nouveau, car IntEnum et IntFlag violent certains principes sémantiques d'une énumération (en pouvant être comparées à des entiers et donc, par transitivité, à d'autres énumérations). IntEnum et IntFlag ne doivent être utilisées que dans les cas où Enum et Flag ne suffisent pas ; par exemple quand des constantes entières sont remplacées par des énumérations, ou pour l’interopérabilité avec d'autres systèmes.

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. Les chaînes littérales formatées : str.format() et format() appellent la méthode __format__() du type dérivé à moins que __str__() ou __format__() soit surchargée dans la sous-classe, auquel cas les méthodes surchargées ou celles de la classe Enum seront utilisées. Pour appeler les méthodes __str__() ou __repr__() de la classe Enum, il faut utiliser les codes de formatage !s ou !r.

Quand utiliser __new__() ou __init__()

__new__() doit être utilisé dès que vous souhaitez personnaliser la valeur effective des membres d'un Enum. Tout autre modification peut autant aller dans __new__() que dans __init__(), mais l'usage de __init__() est recommandé.

Par exemple, si vous voulez passer plusieurs éléments au constructeur, mais qu'un seul d'entre eux soit la valeur :

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Exemples intéressants

Bien que Enum, IntEnum, IntFlag et Flag soient conçues pour répondre à la majorité des besoins, elles ne peuvent répondre à tous. Voici quelques recettes d'énumération qui peuvent être réutilisées telles quelles, ou peuvent servir d'exemple pour développer vos propres énumérations.

Omettre les valeurs

Dans de nombreux cas, la valeur réelle de l'énumération n'a pas d'importance. Il y a plusieurs façons de définir ce type d'énumération simple :

  • affecter des instances de auto aux valeurs

  • affecter des instances de object aux valeurs

  • affecter des chaînes de caractères aux valeurs pour les décrire

  • affecter un n-uplet aux valeurs et définir une méthode __new__() pour remplacer les n-uplets avec un int

Utiliser une de ces méthodes indique à l'utilisateur que les valeurs n'ont pas d'importance. Cela permet aussi d'ajouter, de supprimer ou de ré-ordonner les membres sans avoir à ré-énumérer les membres existants.

Quelle que soit la méthode choisie, il faut fournir une méthode repr() qui masque les valeurs (pas importantes de toute façon) :

>>> class NoValue(Enum):
...     def __repr__(self):
...         return '<%s.%s>' % (self.__class__.__name__, self.name)
...

Avec auto

On utilise auto de la manière suivante :

>>> class Color(NoValue):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN>

Avec object

On utilise object de la manière suivante :

>>> class Color(NoValue):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN>

Avec une chaîne de caractères de description

On utilise une chaîne de caractères de la manière suivante :

>>> class Color(NoValue):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
'go'

Avec une méthode ad-hoc __new__()

On utilise une méthode __new__() d'énumération de la manière suivante :

>>> class AutoNumber(NoValue):
...     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
<Color.GREEN>
>>> Color.GREEN.value
2

Pour définir un AutoNumber plus générique, ajoutez *args à la signature :

>>> class AutoNumber(NoValue):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

Ainsi, quand vous héritez d'AutoNumber, vous pouvez définir __init__ pour gérer tout argument supplémentaire :

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

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.

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

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().

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

TimePeriod

Exemple d'utilisation de l'attribut _ignore_ :

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

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).

Classes Enum

La métaclasse EnumMeta se charge de fournir les méthodes __contains__(), __dir__(), __iter__() etc. qui permettent de faire des opérations sur une classe Enum qui ne fonctionneraient pas sur une classe standard, comme list(Color) ou some_enum_var in Color. EnumMeta garantit que les autres méthodes de la classe finale Enum sont correctes (comme __new__(), __getnewargs__(), __str__() et __repr__()).

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.

Aspects approfondis

Noms de la forme __dunder__ disponibles

__members__ est un dictionnaire en lecture seule ordonné d'éléments nom_du_membre / membre. Il n'est disponible que depuis la classe.

La méthode __new__(), si elle est définie, doit créer et renvoyer les membres de l'énumération ; affecter correctement l'attribut _value_ du membre est également conseillé. Une fois que tous les membres ont été créés, cette méthode n'est plus utilisée.

Noms de la forme _sunder_ disponibles

  • _name_ -- nom du membre

  • _value_ -- valeur du membre ; il est possible d'y accéder ou de la muer dans __new__

  • _missing_ -- une fonction de recherche qui est appelée quand la valeur n'est pas trouvée ; elle peut être redéfinie

  • _ignore_ -- une liste de noms, sous la forme de list() ou de str(), qui ne seront pas transformés en membres, et seront supprimés de la classe résultante

  • _order_ -- utilisé en Python 2 ou 3 pour s'assurer que l'ordre des membres est cohérent (attribut de classe, supprimé durant la création de la classe)

  • _generate_next_value_ -- utilisée par l' API par fonction et par auto pour obtenir une valeur appropriée à affecter à un membre de l'enum ; elle peut être redéfinie

Nouveau dans la version 3.6: _missing_, _order_, _generate_next_value_

Nouveau dans la version 3.7: _ignore_

Pour faciliter la transition de Python 2 en Python 3, l'attribut _order_ peut être défini. Il sera comparé au véritable ordre de l'énumération et lève une erreur si les deux ne correspondent pas :

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_

Note

En Python 2, l'attribut _order_ est indispensable car l'ordre de la définition est perdu avant de pouvoir être enregistré.

Type des membres de Enum

Les membres de Enum sont des instances de leur classe Enum. On y accède normalement par ClasseEnum.membre. Dans certains cas, on peut également y accéder par ClasseEnum.membre.membre, mais ceci est fortement déconseillé car cette indirection est susceptible d'échouer, ou pire, de ne pas renvoyer le membre de la classe Enum désiré (c'est une autre bonne raison pour définir tous les noms des membres en majuscules) :

>>> 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.

Valeur booléenne des classes Enum et de leurs membres

Les membres d'une classe Enum mélangée avec un type non dérivé de Enum (comme int, str, etc.) sont évalués selon les règles du type de mélange. Sinon, tous les membres valent True. Pour faire dépendre l'évaluation booléenne de votre propre Enum de la valeur du membre, il faut ajouter le code suivant à votre classe :

def __bool__(self):
    return bool(self.value)

Les classes Enum valent toujours True.

Classes Enum avec des méthodes

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']

Combinaison de membres de Flag

Si une valeur issue de la combinaison de membres de Flag n'est pas associée explicitement à un membre, la fonction repr() inclut tous les membres et toutes les combinaisons de membres présents dans cette valeur :

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>