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

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

   Les instances de cette classe remplacent les membres d'une *Enum*
   par une valeur appropriée.

Nouveau dans la version 3.6: "Flag", "IntFlag", "auto"


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:

  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


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


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. 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 par défaut "_generate_next_value_()" doit fournir le
  "int" suivant de la séquence en fonction du dernier "int" fourni,
  mais la séquence générée dépend de l'implémentation Python.


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


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


8.13.9. 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.10. 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.11. 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.


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


8.13.13. Énumérations dérivées
==============================


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


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


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


8.13.13.4. 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. Chaînes de caractères formatées littérales : "str.format()" et
   "format()" appellent la méthode "__format__()" du type de mélange.
   Pour appeler les fonctions "str()" ou "repr()" de la classe "Enum",
   il faut utiliser les codes de formatage *!s* ou *!r*.


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


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


8.13.14.1.1. Avec "auto"
~~~~~~~~~~~~~~~~~~~~~~~~

On utilise "auto" de la manière suivante :

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


8.13.14.1.2. Avec "object"
~~~~~~~~~~~~~~~~~~~~~~~~~~

On utilise "object" de la manière suivante :

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


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


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

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.14.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.14.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.14.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.15. 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.15.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.15.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.15.3. Aspects approfondis
------------------------------


8.13.15.3.1. Noms de la forme "__dunder__" disponibles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

"__members__" est une "OrderedDict" de correspondances "nom_du_membre"
/ "membre". Elle 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.


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

* "_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_"

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


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


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


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


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