enum
— Énumérations¶
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 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 deIntFlag
sont aussi des sous-classes deint
.
-
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
¶ Les instances sont remplacées par une valeur appropriée pour les membres de l'énumération. Par défaut, la valeur initiale démarre à 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
estRED
, la valeur deColor.BLUE
est3
, 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 votre énumération définit __new__()
ou __init__()
, alors les valeurs affectées aux membres seront passées à ces méthodes. Voir Planet pour exemple.
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: MoreColor: cannot extend enumeration 'Color'
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 n-uplets (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 :
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 qu'un type est spécifié à la déclaration de la classe, alors tous les membres doivent être de ce type, p. ex.int
ci-dessus. Cette restriction ne s'applique pas aux classes dérivées qui n'ajoutent que des méthodes supplémentaires sans spécifier un type de données.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.Les chaînes littérales formatées :
str.format()
etformat()
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 classeEnum
seront utilisées. Pour appeler les méthodes__str__()
ou__repr__()
de la classeEnum
, 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 valeursaffecter des instances de
object
aux valeursaffecter 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 unint
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>
>>> 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 leurs 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 d'unelist()
ou d'unestr()
, qui ne seront pas convertis 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 parauto
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é.
_Noms__privés¶
Les noms privés seront des attributs habituels avec Python 3.11 et non plus des erreurs ou membres (selon si le nom termine par tiret bas ou non). Utiliser ces noms avec Python 3.10 lèvera un DeprecationWarning
.
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
Note
Ce comportement est obsolète et sera retiré en 3.11.
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)
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__', 'mass', 'name', 'radius', '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>
Note
En 3.11, les combinaisons non-nommées de Flag
ne renverront que les flags canoniques (flags associés à une unique valeur). Donc Color(7)
renverra <Color.BLUE|GREE|RED: 7>
.