Question

Je suis principalement développeur C#, mais je travaille actuellement sur un projet en Python.

Comment puis-je représenter l’équivalent d’un Enum en Python ?

Était-ce utile?

La solution

Des énumérations ont été ajoutées à Python 3.4 comme décrit dans PPE 435.Cela a également été rétroporté vers 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 et 2.4 sur pypi.

Pour des techniques Enum plus avancées, essayez le bibliothèque aénum (2.7, 3.3+, même auteur que enum34.Le code n'est pas parfaitement compatible entre py2 et py3, par ex.tu auras besoin __order__ en python 2).

  • Utiliser enum34, faire $ pip install enum34
  • Utiliser aenum, faire $ pip install aenum

Installation enum (pas de chiffres) installera une version complètement différente et incompatible.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

ou équivalent:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

Dans les versions antérieures, une façon de réaliser des énumérations était :

def enum(**enums):
    return type('Enum', (), enums)

qui s'utilise ainsi :

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

Vous pouvez également facilement prendre en charge l’énumération automatique avec quelque chose comme ceci :

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

et utilisé comme ceci :

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

La prise en charge de la reconversion des valeurs en noms peut être ajoutée de cette manière :

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

Cela écrase tout ce qui porte ce nom, mais cela est utile pour restituer vos énumérations en sortie.Il lancera KeyError si le mappage inverse n'existe pas.Avec le premier exemple :

>>> Numbers.reverse_mapping['three']
'THREE'

Autres conseils

Avant la PEP 435, Python n'avait pas d'équivalent mais vous pouviez implémenter le vôtre.

Moi, j'aime faire simple (j'ai vu des exemples horriblement complexes sur le net), un truc comme ça...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Dans Python 3.4 (PPE 435), tu peux faire Énumération la classe de base.Cela vous offre un peu de fonctionnalités supplémentaires, décrites dans le PEP.Par exemple, les membres d'une énumération sont distincts des entiers et sont composés d'un name et un value.

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

Si vous ne souhaitez pas saisir les valeurs, utilisez le raccourci suivant :

class Animal(Enum):
    DOG, CAT = range(2)

Enum implémentations peuvent être convertis en listes et sont itérables.L'ordre de ses membres est l'ordre de déclaration et n'a rien à voir avec leurs valeurs.Par exemple:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

Voici une implémentation :

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Voici son utilisation :

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)

Si vous avez besoin de valeurs numériques, voici le moyen le plus rapide :

dog, cat, rabbit = range(3)

Dans Python 3.x, vous pouvez également ajouter un espace réservé étoilé à la fin, qui absorbera toutes les valeurs restantes de la plage au cas où cela ne vous dérangerait pas de perdre de la mémoire et de ne pas pouvoir compter :

dog, cat, rabbit, horse, *_ = range(100)

La meilleure solution pour vous dépendra de ce que vous attendez de votre faux enum.

Énumération simple :

Si vous avez besoin du enum comme seulement une liste de des noms identifier différents articles, la solution par Marc Harrison (ci-dessus) est génial :

Pen, Pencil, Eraser = range(0, 3)

Utilisant un range vous permet également de définir n'importe quel valeur de départ:

Pen, Pencil, Eraser = range(9, 12)

En plus de ce qui précède, si vous exigez également que les éléments appartiennent à un récipient d'une manière ou d'une autre, puis intégrez-les dans une classe :

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Pour utiliser l'élément enum, vous devez maintenant utiliser le nom du conteneur et le nom de l'élément :

stype = Stationery.Pen

Énumération complexe :

Pour de longues listes d’énumérations ou des utilisations plus compliquées d’énumérations, ces solutions ne suffiront pas.Vous pouvez consulter la recette de Will Ware pour Simulation d'énumérations en Python publié dans le Livre de recettes Python.Une version en ligne de celui-ci est disponible ici.

Plus d'informations:

PEP 354 :Énumérations en Python contient les détails intéressants d'une proposition d'énumération en Python et pourquoi elle a été rejetée.

Le modèle d'énumération de typeSafe qui a été utilisé dans Java Pre-JDK 5 présente un certain nombre d'avantages.Tout comme dans la réponse d'Alexandru, vous créez une classe et des champs au niveau de la classe sont les valeurs d'énumération;Cependant, les valeurs d'énumération sont des instances de la classe plutôt que de petits entiers.Cela a l'avantage que vos valeurs d'énumération ne se comparent pas par inadvertance égale aux petits entiers, vous pouvez contrôler la façon dont ils sont imprimés, ajouter des méthodes arbitraires si cela est utile et faire des affirmations en utilisant Isinstance:

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")

>>> x = Animal.DOG
>>> x
<Animal: dog>
>>> x == 1
False

Une récente fil de discussion sur python-dev a souligné qu'il existe quelques bibliothèques d'énumérations dans la nature, notamment :

Une classe Enum peut être une seule ligne.

class Enum(tuple): __getattr__ = tuple.index

Comment l'utiliser (recherche directe et inversée, clés, valeurs, éléments, etc.)

>>> State = Enum(['Unclaimed', 'Claimed'])
>>> State.Claimed
1
>>> State[1]
'Claimed'
>>> State
('Unclaimed', 'Claimed')
>>> range(len(State))
[0, 1]
>>> [(k, State[k]) for k in range(len(State))]
[(0, 'Unclaimed'), (1, 'Claimed')]
>>> [(k, getattr(State, k)) for k in State]
[('Unclaimed', 0), ('Claimed', 1)]

Python n'a pas d'équivalent intégré à enum, et d'autres réponses ont des idées pour mettre en œuvre les vôtres (vous pourriez également être intéressé par le sur la version supérieure dans le livre de recettes Python).

Cependant, dans les situations où un enum serait demandé en C, je finis généralement par en utilisant simplement des chaînes simples:en raison de la façon dont les objets/attributs sont implémentés, (C)Python est optimisé pour fonctionner de toute façon très rapidement avec des chaînes courtes, il n'y aurait donc aucun avantage en termes de performances à utiliser des entiers.Pour vous prémunir contre les fautes de frappe/valeurs invalides, vous pouvez insérer des chèques à des endroits sélectionnés.

ANIMALS = ['cat', 'dog', 'python']

def take_for_a_walk(animal):
    assert animal in ANIMALS
    ...

(Un inconvénient par rapport à l'utilisation d'une classe est que vous perdez le bénéfice de la saisie semi-automatique)

Donc, je suis d'accord.N'appliquons pas la sécurité des types en Python, mais j'aimerais me protéger des erreurs stupides.Alors qu’en pensons-nous ?

class Animal(object):
    values = ['Horse','Dog','Cat']

    class __metaclass__(type):
        def __getattr__(self, name):
            return self.values.index(name)

Cela m'évite les collisions de valeurs lors de la définition de mes énumérations.

>>> Animal.Cat
2

Il y a un autre avantage pratique :recherches inversées très rapides :

def name_of(self, i):
    return self.values[i]

Le 10/05/2013, Guido a accepté d'accepter PPE 435 dans la bibliothèque standard Python 3.4.Cela signifie que Python a enfin un support intégré pour les énumérations !

Un rétroportage est disponible pour Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 et 2.4.C'est sur Pypi comme énumération34.

Déclaration:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

Représentation:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

Itération:

>>> for color in Color:
...   print(color)
...
Color.red
Color.green
Color.blue

Accès programmatique :

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

Pour plus d'informations, reportez-vous à La proposition.La documentation officielle suivra probablement bientôt.

Je préfère définir les énumérations en Python comme ceci :

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

C'est plus résistant aux bugs que d'utiliser des entiers puisque vous n'avez pas à vous soucier de vous assurer que les entiers sont uniques (par ex.si tu disais Chien = 1 et Chat = 1 tu serais foutu).

C'est plus résistant aux bogues que l'utilisation de chaînes puisque vous n'avez pas à vous soucier des fautes de frappe (par ex.x == "catt" échoue silencieusement, mais x == Animal.Catt est une exception d'exécution).

def M_add_class_attribs(attribs):
    def foo(name, bases, dict_):
        for v, k in attribs:
            dict_[k] = v
        return type(name, bases, dict_)
    return foo

def enum(*names):
    class Foo(object):
        __metaclass__ = M_add_class_attribs(enumerate(names))
        def __setattr__(self, name, value):  # this makes it read-only
            raise NotImplementedError
    return Foo()

Utilisez-le comme ceci :

Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.DOG = 2 # raises NotImplementedError

si vous voulez juste des symboles uniques et que vous ne vous souciez pas des valeurs, remplacez cette ligne :

__metaclass__ = M_add_class_attribs(enumerate(names))

avec ça:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)

Hmmm...Je suppose que la chose la plus proche d'une énumération serait un dictionnaire, défini comme ceci :

months = {
    'January': 1,
    'February': 2,
    ...
}

ou

months = dict(
    January=1,
    February=2,
    ...
)

Ensuite, vous pouvez utiliser le nom symbolique des constantes comme ceci :

mymonth = months['January']

Il existe d'autres options, comme une liste de tuples, ou un tuple de tuples, mais le dictionnaire est le seul qui vous fournit un moyen "symbolique" (chaîne constante) d'accéder à la valeur.

Modifier:J'aime aussi la réponse d'Alexandru !

Une autre implémentation très simple d'une énumération en Python, en utilisant namedtuple:

from collections import namedtuple

def enum(*keys):
    return namedtuple('Enum', keys)(*keys)

MyEnum = enum('FOO', 'BAR', 'BAZ')

Ou bien,

# With sequential number values
def enum(*keys):
    return namedtuple('Enum', keys)(*range(len(keys)))

# From a dict / keyword args
def enum(**kwargs):
    return namedtuple('Enum', kwargs.keys())(*kwargs.values())

Comme la méthode ci-dessus qui sous-classe set, Ceci permet:

'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO

Mais il a plus de flexibilité car il peut avoir différentes clés et valeurs.Ceci permet

MyEnum.FOO < MyEnum.BAR

pour agir comme prévu si vous utilisez la version qui remplit des valeurs numériques séquentielles.

Ce que j'utilise :

class Enum(object):
    def __init__(self, names, separator=None):
        self.names = names.split(separator)
        for value, name in enumerate(self.names):
            setattr(self, name.upper(), value)
    def tuples(self):
        return tuple(enumerate(self.names))

Comment utiliser:

>>> state = Enum('draft published retracted')
>>> state.DRAFT
0
>>> state.RETRACTED
2
>>> state.FOO
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'Enum' object has no attribute 'FOO'
>>> state.tuples()
((0, 'draft'), (1, 'published'), (2, 'retracted'))

Cela vous donne donc des constantes entières comme state.PUBLISHED et les deux tuples à utiliser comme choix dans les modèles Django.

À partir de Python 3.4, les énumérations seront officiellement prises en charge.Vous pouvez trouver de la documentation et des exemples ici sur la page de documentation Python 3.4.

Les énumérations sont créées à l'aide de la syntaxe de classe, ce qui les rend faciles à lire et à écrire.Une méthode de création alternative est décrite dans l'API fonctionnelle.Pour définir une énumération, sous-classez Enum comme suit :

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3

Davidg recommande d'utiliser des dictés.J'irais plus loin et utiliserais des ensembles :

months = set('January', 'February', ..., 'December')

Vous pouvez maintenant tester si une valeur correspond à l'une des valeurs de l'ensemble, comme ceci :

if m in months:

comme dF, cependant, j'utilise généralement simplement des constantes de chaîne à la place des énumérations.

C'est le meilleur que j'ai vu :"Énumérations de première classe en Python"

http://code.activestate.com/recipes/413486/

Cela vous donne une classe et la classe contient toutes les énumérations.Les énumérations peuvent être comparées les unes aux autres, mais n'ont pas de valeur particulière ;vous ne pouvez pas les utiliser comme valeur entière.(J'ai résisté à cela au début parce que je suis habitué aux énumérations C, qui sont des valeurs entières.Mais si vous ne pouvez pas l'utiliser comme entier, vous ne pouvez pas l'utiliser comme entier par erreur, donc dans l'ensemble, je pense que c'est une victoire.) Chaque énumération est une valeur unique.Vous pouvez imprimer des énumérations, vous pouvez les parcourir, vous pouvez tester qu'une valeur d'énumération est "dans" l'énumération.C'est assez complet et astucieux.

Modifier (cfi) :Le lien ci-dessus n'est pas compatible avec Python 3.Voici mon portage d'enum.py vers Python 3 :

def cmp(a,b):
   if a < b: return -1
   if b < a: return 1
   return 0


def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __iter__(self):        return iter(constants)
      def __len__(self):         return len(constants)
      def __getitem__(self, i):  return constants[i]
      def __repr__(self):        return 'Enum' + str(names)
      def __str__(self):         return 'enum ' + str(constants)

   class EnumValue(object):
      __slots__ = ('__value')
      def __init__(self, value): self.__value = value
      Value = property(lambda self: self.__value)
      EnumType = property(lambda self: EnumType)
      def __hash__(self):        return hash(self.__value)
      def __cmp__(self, other):
         # C fans might want to remove the following assertion
         # to make all enums comparable by ordinal value {;))
         assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
         return cmp(self.__value, other.__value)
      def __lt__(self, other):   return self.__cmp__(other) < 0
      def __eq__(self, other):   return self.__cmp__(other) == 0
      def __invert__(self):      return constants[maximum - self.__value]
      def __nonzero__(self):     return bool(self.__value)
      def __repr__(self):        return str(names[self.__value])

   maximum = len(names) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = tuple(constants)
   EnumType = EnumClass()
   return EnumType


if __name__ == '__main__':
   print( '\n*** Enum Demo ***')
   print( '--- Days of week ---')
   Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')
   print( Days)
   print( Days.Mo)
   print( Days.Fr)
   print( Days.Mo < Days.Fr)
   print( list(Days))
   for each in Days:
      print( 'Day:', each)
   print( '--- Yes/No ---')
   Confirmation = Enum('No', 'Yes')
   answer = Confirmation.No
   print( 'Your answer is not', ~answer)

Rester simple:

class Enum(object): 
    def __init__(self, tupleList):
            self.tupleList = tupleList

    def __getattr__(self, name):
            return self.tupleList.index(name)

Alors:

DIRECTION = Enum(('UP', 'DOWN', 'LEFT', 'RIGHT'))
DIRECTION.DOWN
1

J'ai eu l'occasion d'avoir besoin d'une classe Enum, dans le but de décoder un format de fichier binaire.Les fonctionnalités que je souhaitais sont une définition concise de l'énumération, la possibilité de créer librement des instances de l'énumération par valeur entière ou par chaîne, et un outil utile. reprprésentation.Voici ce que j'ai obtenu :

>>> class Enum(int):
...     def __new__(cls, value):
...         if isinstance(value, str):
...             return getattr(cls, value)
...         elif isinstance(value, int):
...             return cls.__index[value]
...     def __str__(self): return self.__name
...     def __repr__(self): return "%s.%s" % (type(self).__name__, self.__name)
...     class __metaclass__(type):
...         def __new__(mcls, name, bases, attrs):
...             attrs['__slots__'] = ['_Enum__name']
...             cls = type.__new__(mcls, name, bases, attrs)
...             cls._Enum__index = _index = {}
...             for base in reversed(bases):
...                 if hasattr(base, '_Enum__index'):
...                     _index.update(base._Enum__index)
...             # create all of the instances of the new class
...             for attr in attrs.keys():
...                 value = attrs[attr]
...                 if isinstance(value, int):
...                     evalue = int.__new__(cls, value)
...                     evalue._Enum__name = attr
...                     _index[value] = evalue
...                     setattr(cls, attr, evalue)
...             return cls
... 

Un exemple fantaisiste de son utilisation :

>>> class Citrus(Enum):
...     Lemon = 1
...     Lime = 2
... 
>>> Citrus.Lemon
Citrus.Lemon
>>> 
>>> Citrus(1)
Citrus.Lemon
>>> Citrus(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __new__
KeyError: 5
>>> class Fruit(Citrus):
...     Apple = 3
...     Banana = 4
... 
>>> Fruit.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True

Principales caractéristiques:

  • str(), int() et repr() tous produisent la sortie la plus utile possible, respectivement le nom de l'énumération, sa valeur entière et une expression Python qui renvoie l'évaluation à l'énumération.
  • Les valeurs énumérées renvoyées par le constructeur sont strictement limitées aux valeurs prédéfinies, sans valeurs d'énumération accidentelles.
  • Les valeurs énumérées sont des singletons ;ils peuvent être strictement comparés à is

J'aime beaucoup la solution d'Alec Thomas (http://stackoverflow.com/a/1695250) :

def enum(**enums):
    '''simple constant "enums"'''
    return type('Enum', (object,), enums)

C'est élégant et épuré, mais c'est juste une fonction qui crée une classe avec les attributs spécifiés.

Avec une petite modification de la fonction, nous pouvons lui faire agir un peu plus « enumy » :

NOTE:J'ai créé les exemples suivants en essayant de reproduire le comportement du nouveau style de Pygtk «Enum» (comme gtk.messagetype.warning)

def enum_base(t, **enums):
    '''enums with a base class'''
    T = type('Enum', (t,), {})
    for key,val in enums.items():
        setattr(T, key, T(val))

    return T

Cela crée une énumération basée sur un type spécifié.En plus de donner un accès aux attributs comme la fonction précédente, elle se comporte comme on peut s'y attendre d'un Enum en ce qui concerne les types.Il hérite également de la classe de base.

Par exemple, des énumérations entières :

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> type(Numbers)
<type 'type'>
>>> type(Numbers.ONE)
<class 'Enum'>
>>> isinstance(x, Numbers)
True

Une autre chose intéressante qui peut être faite avec cette méthode est de personnaliser un comportement spécifique en remplaçant les méthodes intégrées :

def enum_repr(t, **enums):
    '''enums with a base class and repr() output'''
    class Enum(t):
        def __repr__(self):
            return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)

    for key,val in enums.items():
        i = Enum(val)
        i._name = key
        setattr(Enum, key, i)

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Si vous le nommez, c'est votre problème, mais sinon, créer des objets au lieu de valeurs vous permet de faire ceci :

>>> DOG = enum('BARK', 'WALK', 'SIT')
>>> CAT = enum('MEOW', 'WALK', 'SIT')
>>> DOG.WALK == CAT.WALK
False

Lorsque vous utilisez d'autres implémentations présentées ici (également lorsque vous utilisez des instances nommées dans mon exemple), vous devez vous assurer de ne jamais essayer de comparer des objets de différentes énumérations.Car voici un écueil possible :

>>> DOG = enum('BARK'=1, 'WALK'=2, 'SIT'=3)
>>> CAT = enum('WALK'=1, 'SIT'=2)
>>> pet1_state = DOG.BARK
>>> pet2_state = CAT.WALK
>>> pet1_state == pet2_state
True

Ouais !

Le nouveau standard en Python est PPE 435, donc une classe Enum sera disponible dans les futures versions de Python :

>>> from enum import Enum

Cependant, pour commencer à l'utiliser maintenant, vous pouvez installer le bibliothèque originale qui a motivé le PEP :

$ pip install flufl.enum

Alors vous peut l'utiliser selon son guide en ligne:

>>> from flufl.enum import Enum
>>> class Colors(Enum):
...     red = 1
...     green = 2
...     blue = 3
>>> for color in Colors: print color
Colors.red
Colors.green
Colors.blue

Le paquet enum de PyPI fournit une implémentation robuste des énumérations.Une réponse antérieure mentionnait le PEP 354 ;cela a été rejeté mais la proposition a été mise en œuvrehttp://pypi.python.org/pypi/enum.

L'utilisation est simple et élégante :

>>> from enum import Enum
>>> Colors = Enum('red', 'blue', 'green')
>>> shirt_color = Colors.green
>>> shirt_color = Colors[2]
>>> shirt_color > Colors.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'

La suggestion d'Alexandru d'utiliser des constantes de classe pour les énumérations fonctionne plutôt bien.

J'aime également ajouter un dictionnaire pour chaque ensemble de constantes afin de rechercher une représentation sous forme de chaîne lisible par l'homme.

Cela répond à deux objectifs :a) il fournit un moyen simple d'imprimer joliment votre énumération et b) le dictionnaire regroupe logiquement les constantes afin que vous puissiez tester l'adhésion.

class Animal:    
  TYPE_DOG = 1
  TYPE_CAT = 2

  type2str = {
    TYPE_DOG: "dog",
    TYPE_CAT: "cat"
  }

  def __init__(self, type_):
    assert type_ in self.type2str.keys()
    self._type = type_

  def __repr__(self):
    return "<%s type=%s>" % (
        self.__class__.__name__, self.type2str[self._type].upper())

Voici une approche avec différentes caractéristiques que je trouve utiles :

  • permet une comparaison > et < basée sur l'ordre dans l'énumération, et non sur l'ordre lexical
  • peut adresser un élément par son nom, sa propriété ou son index :x.a, x['a'] ou x[0]
  • prend en charge les opérations de découpage comme [:] ou [-1]

et, surtout empêche les comparaisons entre les énumérations de différents types!

Basé étroitement sur http://code.activestate.com/recipes/413486-first-class-enums-in-python.

De nombreux doctests sont inclus ici pour illustrer ce qui est différent de cette approche.

def enum(*names):
    """
SYNOPSIS
    Well-behaved enumerated type, easier than creating custom classes

DESCRIPTION
    Create a custom type that implements an enumeration.  Similar in concept
    to a C enum but with some additional capabilities and protections.  See
    http://code.activestate.com/recipes/413486-first-class-enums-in-python/.

PARAMETERS
    names       Ordered list of names.  The order in which names are given
                will be the sort order in the enum type.  Duplicate names
                are not allowed.  Unicode names are mapped to ASCII.

RETURNS
    Object of type enum, with the input names and the enumerated values.

EXAMPLES
    >>> letters = enum('a','e','i','o','u','b','c','y','z')
    >>> letters.a < letters.e
    True

    ## index by property
    >>> letters.a
    a

    ## index by position
    >>> letters[0]
    a

    ## index by name, helpful for bridging string inputs to enum
    >>> letters['a']
    a

    ## sorting by order in the enum() create, not character value
    >>> letters.u < letters.b
    True

    ## normal slicing operations available
    >>> letters[-1]
    z

    ## error since there are not 100 items in enum
    >>> letters[99]
    Traceback (most recent call last):
        ...
    IndexError: tuple index out of range

    ## error since name does not exist in enum
    >>> letters['ggg']
    Traceback (most recent call last):
        ...
    ValueError: tuple.index(x): x not in tuple

    ## enums must be named using valid Python identifiers
    >>> numbers = enum(1,2,3,4)
    Traceback (most recent call last):
        ...
    AssertionError: Enum values must be string or unicode

    >>> a = enum('-a','-b')
    Traceback (most recent call last):
        ...
    TypeError: Error when calling the metaclass bases
        __slots__ must be identifiers

    ## create another enum
    >>> tags = enum('a','b','c')
    >>> tags.a
    a
    >>> letters.a
    a

    ## can't compare values from different enums
    >>> letters.a == tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    >>> letters.a < tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    ## can't update enum after create
    >>> letters.a = 'x'
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'a' is read-only

    ## can't update enum after create
    >>> del letters.u
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'u' is read-only

    ## can't have non-unique enum values
    >>> x = enum('a','b','c','a')
    Traceback (most recent call last):
        ...
    AssertionError: Enums must not repeat values

    ## can't have zero enum values
    >>> x = enum()
    Traceback (most recent call last):
        ...
    AssertionError: Empty enums are not supported

    ## can't have enum values that look like special function names
    ## since these could collide and lead to non-obvious errors
    >>> x = enum('a','b','c','__cmp__')
    Traceback (most recent call last):
        ...
    AssertionError: Enum values beginning with __ are not supported

LIMITATIONS
    Enum values of unicode type are not preserved, mapped to ASCII instead.

    """
    ## must have at least one enum value
    assert names, 'Empty enums are not supported'
    ## enum values must be strings
    assert len([i for i in names if not isinstance(i, types.StringTypes) and not \
        isinstance(i, unicode)]) == 0, 'Enum values must be string or unicode'
    ## enum values must not collide with special function names
    assert len([i for i in names if i.startswith("__")]) == 0,\
        'Enum values beginning with __ are not supported'
    ## each enum value must be unique from all others
    assert names == uniquify(names), 'Enums must not repeat values'

    class EnumClass(object):
        """ See parent function for explanation """

        __slots__ = names

        def __iter__(self):
            return iter(constants)

        def __len__(self):
            return len(constants)

        def __getitem__(self, i):
            ## this makes xx['name'] possible
            if isinstance(i, types.StringTypes):
                i = names.index(i)
            ## handles the more normal xx[0]
            return constants[i]

        def __repr__(self):
            return 'enum' + str(names)

        def __str__(self):
            return 'enum ' + str(constants)

        def index(self, i):
            return names.index(i)

    class EnumValue(object):
        """ See parent function for explanation """

        __slots__ = ('__value')

        def __init__(self, value):
            self.__value = value

        value = property(lambda self: self.__value)

        enumtype = property(lambda self: enumtype)

        def __hash__(self):
            return hash(self.__value)

        def __cmp__(self, other):
            assert self.enumtype is other.enumtype, 'Only values from the same enum are comparable'
            return cmp(self.value, other.value)

        def __invert__(self):
            return constants[maximum - self.value]

        def __nonzero__(self):
            ## return bool(self.value)
            ## Original code led to bool(x[0])==False, not correct
            return True

        def __repr__(self):
            return str(names[self.value])

    maximum = len(names) - 1
    constants = [None] * len(names)
    for i, each in enumerate(names):
        val = EnumValue(i)
        setattr(EnumClass, each, val)
        constants[i] = val
    constants = tuple(constants)
    enumtype = EnumClass()
    return enumtype

Voici une variante sur La solution d'Alec Thomas:

def enum(*args, **kwargs):
    return type('Enum', (), dict((y, x) for x, y in enumerate(args), **kwargs)) 

x = enum('POOH', 'TIGGER', 'EEYORE', 'ROO', 'PIGLET', 'RABBIT', 'OWL')
assert x.POOH == 0
assert x.TIGGER == 1

Cette solution est un moyen simple d'obtenir une classe pour l'énumération définie sous forme de liste (plus d'affectations d'entiers ennuyeuses) :

énumération.py :

import new

def create(class_name, names):
    return new.classobj(
        class_name, (object,), dict((y, x) for x, y in enumerate(names))
    )

exemple.py :

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))

Alors que la proposition d'énumération originale, PPE 354, a été rejeté il y a des années, il revient sans cesse.Une sorte d'énumération était destinée à être ajoutée à la version 3.2, mais elle a été repoussée à la version 3.3 puis oubliée.Et maintenant il y a un PPE 435 destiné à être inclus dans Python 3.4.L'implémentation de référence du PEP 435 est flufl.enum.

En avril 2013, il semble y avoir un consensus général selon lequel quelque chose devrait être ajouté à la bibliothèque standard dans la version 3.4, à condition que les gens puissent se mettre d'accord sur ce que devrait être ce « quelque chose ».C'est la partie la plus difficile.Voir les discussions commençant ici et ici, et une demi-douzaine d'autres fils de discussion au cours des premiers mois de 2013.

Pendant ce temps, chaque fois que cela se produit, une multitude de nouvelles conceptions et implémentations apparaissent sur PyPI, ActiveState, etc., donc si vous n'aimez pas la conception FLUFL, essayez un Recherche PyPI.

Une variante (avec prise en charge pour obtenir le nom d'une valeur enum) pour La réponse intéressante d'Alec Thomas:

class EnumBase(type):
    def __init__(self, name, base, fields):
        super(EnumBase, self).__init__(name, base, fields)
        self.__mapping = dict((v, k) for k, v in fields.iteritems())
    def __getitem__(self, val):
        return self.__mapping[val]

def enum(*seq, **named):
    enums = dict(zip(seq, range(len(seq))), **named)
    return EnumBase('Enum', (), enums)

Numbers = enum(ONE=1, TWO=2, THREE='three')
print Numbers.TWO
print Numbers[Numbers.ONE]
print Numbers[2]
print Numbers['three']
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top