Comment représenter un 'Enum' en Python?

1143

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

Comment représenter l'équivalent d'un Enum en Python?

secte
la source

Réponses:

2689

Des énumérations ont été ajoutées à Python 3.4 comme décrit dans PEP 435 . Il a également été rétroporté à 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 la bibliothèque aenum (2.7, 3.3+, même auteur que enum34. Le code n'est pas parfaitement compatible entre py2 et py3, par exemple vous aurez besoin __order__en python 2 ).

  • Pour utiliser enum34, faites$ pip install enum34
  • Pour utiliser aenum, faites$ pip install aenum

L'installation enum(sans numéro) 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 les énumérations est:

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

qui est utilisé comme ça:

>>> 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 ça:

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

La prise en charge de la conversion des valeurs en noms peut être ajoutée de cette façon:

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 il est utile pour rendre vos énumérations en sortie. Il lancera KeyError si le mappage inverse n'existe pas. Avec le premier exemple:

>>> Numbers.reverse_mapping['three']
'THREE'
Alec Thomas
la source
1
Je n'ai pas pu comprendre, pourquoi ont-ils passé kwargs (** nommé) dans la méthode enum (* séquentielle, ** nommée)? Veuillez expliquer. Sans kwargs, cela fonctionnera également. Je l'ai vérifié.
Seenu S
Ce serait bien de mettre à jour la fonction Python 2 pour qu'elle soit compatible avec l'API fonctionnelle d'Enum de Python 3 (nom, valeurs)
bscan
La var kwargs ( **named) de la fonction enum pour les anciennes versions prend en charge les valeurs personnalisées:enum("blue", "red", "green", black=0)
Éric Araujo
823

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

Moi, j'aime rester simple (j'ai vu des exemples horriblement complexes sur le net), quelque chose comme ça ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

Dans Python 3.4 ( PEP 435 ), vous pouvez faire de Enum la classe de base. Cela vous donne un peu de fonctionnalités supplémentaires, décrites dans le PEP. Par exemple, les membres enum sont distincts des entiers, et ils sont composés de a nameet a 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)

Enumles implémentations peuvent être converties 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
Jundiaius
la source
51
Non, c'est une variable de classe.
Georg Schölly
246
Python est dynamique par défaut. Il n'y a aucune raison valable d'appliquer la sécurité à la compilation dans un langage comme Python, surtout lorsqu'il n'y en a pas. Et une autre chose ... un bon modèle n'est bon que dans le contexte dans lequel il a été créé. Un bon modèle peut également être remplacé ou complètement inutile, selon les outils que vous utilisez.
Alexandru Nedelcu
20
@Longpoke si vous avez 100 valeurs, alors vous faites vraiment quelque chose de mal;) J'aime les nombres associés à mes énumérations ... ils sont faciles à écrire (vs les chaînes), peuvent être facilement persistés dans une base de données et sont compatibles avec l'énumération C / C ++, qui facilite le marshaling.
Alexandru Nedelcu
50
J'utilise ceci, avec les numéros remplacés par object().
Tobu
9
Le PEP354 d'origine n'est plus simplement rejeté, mais il est désormais marqué comme remplacé. PEP435 ajoute une énumération standard pour Python 3.4. Voir python.org/dev/peps/pep-0435
Peter Hansen
323

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)
shahjapan
la source
51
Excellent. Cela peut être encore amélioré en remplaçant __setattr__(self, name, value)et peut-être __delattr__(self, name)pour que si vous écrivez accidentellement Animals.DOG = CAT, cela ne réussisse pas en silence.
Joonas Pulakka
15
@shahjapan: Intéressant, mais relativement lent: un test est effectué pour chaque accès comme Animals.DOG; en outre, les valeurs des constats sont des chaînes, de sorte que les comparaisons avec ces constantes sont plus lentes que si, disons, des entiers étaient autorisés comme valeurs.
Eric O Lebigot
3
@shahjapan: Je dirais que cette solution n'est pas aussi lisible que les solutions plus courtes d'Alexandru ou de Mark, par exemple. C'est une solution intéressante, cependant. :)
Eric O Lebigot
J'ai essayé d'utiliser la setattr()fonction dans la __init__()méthode au lieu de la __getattr__()méthode de surenchère . Je suppose que cela est censé fonctionner de la même manière: classe Enum (objet): def __init __ (self, enum_string_list): if type (enum_string_list) == list: for enum_string in enum_string_list: setattr (self, enum_string, enum_string) else: raise AttributeError
Harshith JV
8
@ AndréTerra: comment vérifier l'appartenance à un try-exceptbloc?
bgusach
210

Si vous avez besoin des 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 gaspiller de la mémoire et ne pouvez pas compter:

dog, cat, rabbit, horse, *_ = range(100)
Mark Harrison
la source
1
Mais cela pourrait prendre plus de mémoire!
MJ
Je ne vois pas l'intérêt de l'espace réservé marqué étant donné que Python vérifiera le nombre de valeurs à décompresser (il fera donc le comptage pour vous).
Gabriel Devillers,
@GabrielDevillers, je pense que Python lèvera une exception s'il y a un décalage sur le nombre d'éléments dans le tuple à assigner.
Mark Harrison
1
En effet, c'est le cas dans mon test (Python2,3) mais cela signifie que toute erreur de comptage du programmeur sera rattrapée au premier test (avec un message donnant le décompte correct).
Gabriel Devillers
1
Je ne peux pas compter. L'espace réservé marqué d'une étoile peut-il également corriger mes finances?
javadba
131

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

Enum simple:

Si vous n'avez besoin enumque d'une liste de noms identifiant différents éléments , la solution de Mark Harrison (ci-dessus) est excellente:

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

L'utilisation d'un rangepermet également de définir n'importe quelle valeur de départ :

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

En plus de ce qui précède, si vous avez également besoin que les éléments appartiennent à un conteneur quelconque, 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

Enum complexe:

Pour de longues listes d'énumérations ou des utilisations plus complexes d'énumérations, ces solutions ne suffiront pas. Vous pouvez consulter la recette de Will Ware pour la simulation d'énumérations en Python publiée dans le livre de recettes Python . Une version en ligne de cela est disponible ici .

Plus d'informations:

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

Ashwin
la source
7
avec rangevous pouvez omettre le premier argument si c'est 0
ToonAlfrink
Une autre fausse énumération qui convient à certaines fins est my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). my_enumPeut ensuite être utilisé dans la recherche, par exemple, my_enum['Item0']peut être un index dans une séquence. Vous voudrez peut-être encapsuler le résultat de str.splitdans une fonction qui lève une exception s'il y a des doublons.
Ana Nimbus
Agréable! Pour les drapeaux, vous pouvezFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx
Ceci est la meilleure réponse
Yuriy Pozniak
78

Le modèle d'énumération typesafe utilisé dans Java pré-JDK 5 présente un certain nombre d'avantages. Tout comme dans la réponse d'Alexandru, vous créez une classe et les champs de niveau de classe sont les valeurs d'énumération; cependant, les valeurs enum 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 à de petits entiers, vous pouvez contrôler la façon dont elles sont imprimées, ajouter des méthodes arbitraires si cela est utile et faire des assertions 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

Un récent fil sur python-dev a souligné qu'il y avait quelques bibliothèques d'énumérations dans la nature, notamment:

Aaron Maenpaa
la source
16
Je pense que c'est une très mauvaise approche. Animal.DOG = Animal ("chien") Animal.DOG2 = Animal ("chien") affirme Animal.DOG == Animal.DOG2 échoue ...
Confusion
11
@Confusion L'utilisateur n'est pas censé appeler le constructeur, le fait qu'il y ait même un constructeur est un détail d'implémentation et vous devez communiquer à qui utilise jamais votre code que la création de nouvelles valeurs d'énumération n'a aucun sens et que la sortie du code ne sera pas "faire la bonne chose". Bien sûr, cela ne vous empêche pas d'implémenter Animal.from_name ("dog") -> Animal.DOG.
Aaron Maenpaa
13
"l'avantage que vos valeurs d'énumération ne comparent pas par inadvertance égal à de petits entiers" Quel est l'avantage de cela? Quel est le problème avec la comparaison de votre énumération avec des nombres entiers? Surtout si vous stockez l'énumération dans la base de données, vous voulez généralement qu'elle soit stockée sous forme d'entiers, vous devrez donc la comparer à des entiers à un moment donné.
ibz
3
@Aaaron Maenpaa. correct. C'est toujours une façon brisée et trop compliquée de le faire.
aaronasterling du
4
@AaronMcSmooth Cela dépend vraiment de si vous venez du point de vue C de "les énumérations ne sont que des noms pour quelques entiers" ou de l'approche plus orientée objet où les valeurs d'énumération sont des objets réels et ont des méthodes (c'est ainsi que les énumérations en Java 1.5 sont, et que le modèle d'énumération de type sûr recherchait). Personnellement, je n'aime pas les instructions switch, donc je penche vers des valeurs d'énumération qui sont des objets réels.
Aaron Maenpaa,
61

Une classe Enum peut être une ligne.

class Enum(tuple): __getattr__ = tuple.index

Comment l'utiliser (recherche directe et inverse, 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)]
Zoetic
la source
Je pense que c'est la solution la plus simple et la plus élégante. En python 2.4 (oui, ancien serveur hérité), les tuples n'ont pas d'index. J'ai résolu de remplacer par liste.
Massimo
J'ai essayé cela dans un cahier Jupyter et j'ai découvert que cela ne fonctionnerait pas comme une définition sur une seule ligne, mais que mettre la définition de getattr sur une deuxième ligne (en retrait) serait accepté.
user5920660
Cette solution me permet d'utiliser le inmot - clé pour rechercher des membres, ce qui est bien. Exemple d'utilisation:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini
51

Donc je suis d'accord. N'imposons pas la sécurité des types en Python, mais je voudrais me protéger contre les 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'empêche de collision de valeurs dans la définition de mes énumérations.

>>> Animal.Cat
2

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

def name_of(self, i):
    return self.values[i]
royal
la source
J'aime ça, mais vous pourriez aussi bien verrouiller les valeurs pour l'efficacité avec un tuple? J'ai joué avec et j'ai trouvé une version qui définit self.values ​​à partir des arguments dans init . C'est agréable de pouvoir déclarer Animal = Enum('horse', 'dog', 'cat'). J'attrape également ValueError dans getattr en cas d'élément manquant dans self.values ​​- il semble préférable de déclencher une AttributeError avec la chaîne de nom fournie à la place. Je n'ai pas pu faire fonctionner la métaclasse dans Python 2.7 sur la base de mes connaissances limitées dans ce domaine, mais ma classe Enum personnalisée fonctionne correctement avec les méthodes d'instance directes.
trojjer
49

Python n'a pas d'équivalent intégré à enum, et d'autres réponses ont des idées pour implémenter le vôtre (vous pouvez également être intéressé par la version par dessus dans le livre de recettes Python).

Cependant, dans les situations où un enumserait demandé en C, je finis généralement par utiliser des chaînes simples : en raison de la façon dont les objets / attributs sont mis en œuvre, (C) Python est optimisé pour fonctionner très rapidement avec des chaînes courtes de toute façon, donc il n'y aurait pas ce ne sera pas vraiment un avantage de performance à utiliser des entiers. Pour vous prémunir contre les fautes de frappe / valeurs invalides, vous pouvez insérer des chèques aux 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 l'avantage de la saisie semi-automatique)

dF.
la source
2
Je préfère cette solution. J'aime utiliser des types intégrés dans la mesure du possible.
Seun Osewa
Cette version n'est pas vraiment exagérée. Il a juste beaucoup de code de test fourni
Casebash
1
En fait, la version "correcte" est dans les commentaires et est beaucoup plus complexe - la version principale a un bug mineur.
Casebash
39

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

Un backport est disponible pour Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 et 2.4. C'est sur Pypi comme enum34 .

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.

Danilo Bargen
la source
33

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

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

C'est plus à l'abri des bogues que d'utiliser des entiers car vous n'avez pas à vous soucier de vous assurer que les entiers sont uniques (par exemple, si vous avez dit Dog = 1 et Cat = 1, vous seriez foutu).

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

mbac32768
la source
31
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 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)
utilisateur18695
la source
11
À mon humble avis, il serait plus propre si vous passiez enum(names)à enum(*names)- alors vous pourriez supprimer la parenthèse supplémentaire lors de l'appel.
Chris Lutz
J'aime cette approche. Je l'ai en fait changé pour définir la valeur d'attribut sur la même chaîne que le nom, qui a la belle propriété que Animal.DOG == 'DOG', afin qu'ils se chaîne automatiquement pour vous. (Aide énormément pour imprimer la sortie de débogage.)
Ted Mielczarek
23

À partir de Python 3.4, il y aura un support officiel pour les énumérations. Vous pouvez trouver de la documentation et des exemples ici sur la page de documentation de 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 autre méthode de création est décrite dans l'API fonctionnelle. Pour définir une énumération, sous-classe Enum comme suit:

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3
Cyril Gandon
la source
Le portage arrière est désormais également pris en charge. C'est la voie à suivre.
srock
22

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 pour les 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 offre un moyen "symbolique" (chaîne constante) d'accéder à la valeur.

Edit: j'aime aussi la réponse d'Alexandru!

dguaraglia
la source
Et surtout, vous pouvez facilement itérer sur un dictionnaire si vous avez besoin d'accéder à ses valeurs comme vous avez besoin que ses valeurs de chaîne apparaissent sous forme d'éléments de zone de liste déroulante. Utilisez donc plutôt un dictionnaire pour remplacer les énumérations.
LEMUEL ADANE
22

Une autre implémentation très simple d'une énumération en Python, 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, cela permet:

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

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

agf
la source
20

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.

Luciano Ramalho
la source
17

davidg recommande d'utiliser des dict. Je voudrais aller plus loin et utiliser 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 des constantes de chaîne à la place des énumérations.

smoking
la source
yep !, beaucoup mieux si vous héritez de set et fournissez la méthode getattr !
shahjapan
17

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
danger89
la source
16

C'est le meilleur que j'ai vu: "Enums de première classe en Python"

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

Il 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 un entier, vous ne pouvez pas l'utiliser comme un 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 lisse.

Edit (cfi): Le lien ci-dessus n'est pas compatible Python 3. Voici mon port 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)
cfi
la source
Cette recette a été utilisée comme base pour une PPE, qui a été rejetée. python.org/dev/peps/pep-0354 Une extension que j'aime: les valeurs d'énumération devraient avoir une variable membre qui vous permet d'extraire la valeur entière interne. Il ne devrait pas être possible de transtyper une énumération en un entier par erreur, donc la .__int__()méthode devrait lever une exception pour une énumération; mais il devrait y avoir un moyen d'en retirer la valeur. Et il devrait être possible de définir des valeurs entières spécifiques au moment de la définition de la classe, afin que vous puissiez utiliser une énumération pour des choses comme les constantes du statmodule.
steveha
14

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 justement sont une définition d'énumération concise, la possibilité de créer librement des instances de l'énumération par une valeur entière ou une chaîne, et une reprprésentation utile . Voici ce que j'ai fini avec:

>>> 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 l'utiliser:

>>> 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 réévalue l'énumération.
  • Les valeurs énumérées renvoyées par le constructeur sont strictement limitées aux valeurs prédéfinies, pas de valeurs énumérées accidentelles.
  • Les valeurs énumérées sont des singletons; ils peuvent être strictement comparés àis
TokenMacGuy
la source
J'aime beaucoup l'utilisation d'une superclasse avec sa propre métaclasse, pour faciliter la définition des énumérations. Ce qui manque ici est une méthode __contains__. Je voudrais pouvoir vérifier qu'une variable donnée fait partie de l'énumération - principalement parce que je veux les énumérations pour les valeurs autorisées d'un paramètre de fonction.
xorsyst
Il s'agit en fait d'une version légèrement coupée de celle que j'ai créée à l'origine (que vous pouvez trouver ici: enum_strict.py ) v qui définit une __instancecheck__méthode. Les classes ne sont pas des collections d'instances, c'est donc 1 in Fruitabsurde. Cependant, la version liée prend en charge isinstance(1, Fruit)ce qui serait plus correct en termes de notion de classes et d'instances.
SingleNegationElimination
Mais en oubliant les classes et en pensant en termes d'énumérations, il est logique de les considérer comme une collection. Par exemple, je pourrais avoir une liste de modes d'ouverture de fichiers (MODE.OPEN, MODE.WRITE, etc.). Je veux vérifier les paramètres de ma fonction: si mode en MODE: lit beaucoup mieux que isintance (mode, Mode)
xorsyst
J'ai mis en place quelque chose de très similaire, qui prend en charge plus que des entiers, et est documenté et testé, sur GitHub: github.com/hmeine/named_constants
hans_meine
12

Le nouveau standard en Python est PEP 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 la bibliothèque d'origine qui a motivé le PEP:

$ pip install flufl.enum

Ensuite, vous pouvez 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
Riaz Rizvi
la source
10
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 la création d'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 situées ici (également lorsque vous utilisez des instances nommées dans mon exemple), vous devez être sûr de ne jamais essayer de comparer des objets provenant d'énumérations différentes. 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

Oui!

estani
la source
9

J'aime vraiment 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 propre, 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 l'amener à agir un peu plus 'enumy':

REMARQUE: j'ai créé les exemples suivants en essayant de reproduire le comportement des nouveaux «enums» de style de pygtk (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, il se comporte comme vous vous attendez à ce qu'un Enum respecte les types. Il hérite également de la classe de base.

Par exemple, les é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'
bj0
la source
cette idée de type "base" est soignée :)
MestreLion
oui, notez que vous pouvez également le faire avec la nouvelle énumération
bj0
7

Le package 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 œuvre http://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'
Ashwin
la source
5

La suggestion d'Alexandru d'utiliser des constantes de classe pour les énumérations fonctionne très bien.

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

Cela sert à deux fins: a) il fournit un moyen simple d'imprimer votre énumération et b) le dictionnaire regroupe logiquement les constantes afin que vous puissiez tester l'appartenance.

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())
Rick Harris
la source
5

Voici une approche avec quelques caractéristiques différentes que je trouve précieuses:

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

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

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

De nombreux doctests inclus ici pour illustrer ce qui est différent dans 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
Chris Johnson
la source
3

Voici une variante de 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
Roy Hyunjin Han
la source
3

Cette solution est un moyen simple d'obtenir une classe pour l'énumération définie comme une liste (plus d'affectations entières ennuyeuses):

enumeration.py:

import new

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

example.py:

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))
Michael Truog
la source
2
C'est une façon vraiment ancienne de créer des classes. Pourquoi ne pas simplement utiliser à la type(class_name, (object,), dict(...))place?
terminus du
3

Alors que la proposition d'énumération originale, PEP 354 , a été rejetée il y a des années, elle revient sans cesse. Une sorte d'énumération devait être ajoutée à la 3.2, mais elle a été repoussée à la 3.3 et ensuite oubliée. Et maintenant, il y a un PEP 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 en 3.4 - tant que les gens peuvent s'entendre sur ce que ce «quelque chose» devrait être. Voilà la partie difficile. Voir les discussions commençant ici et ici , et une demi-douzaine d'autres discussions dans les premiers mois de 2013.

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

abarnert
la source
3

Utilisez le suivant.

TYPE = {'EAN13':   u'EAN-13',
        'CODE39':  u'Code 39',
        'CODE128': u'Code 128',
        'i25':     u'Interleaved 2 of 5',}

>>> TYPE.items()
[('EAN13', u'EAN-13'), ('i25', u'Interleaved 2 of 5'), ('CODE39', u'Code 39'), ('CODE128', u'Code 128')]
>>> TYPE.keys()
['EAN13', 'i25', 'CODE39', 'CODE128']
>>> TYPE.values()
[u'EAN-13', u'Interleaved 2 of 5', u'Code 39', u'Code 128']

Je l'ai utilisé pour les choix de modèles de Django , et cela a l'air très pythonique. Ce n'est pas vraiment un Enum, mais il fait le travail.

Natim
la source