Question Comment puis-je représenter un 'Enum' en Python?


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

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


1146


origine


Réponses:


Des enums ont été ajoutés à 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 le bibliothèque d'aenum (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 numéros) va installer 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 d'accomplir des é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 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 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'

2320



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

Moi, j'aime garder les choses simples (j'ai vu des exemples horriblement complexes sur le net), quelque chose comme ça ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

En Python 3.4 (PEP 435), tu peux faire Enum la classe de base. Cela vous apporte un peu de fonctionnalité supplémentaire, décrite dans le PEP. Par exemple, les membres enum sont distincts des entiers, et ils 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 voulez pas saisir les valeurs, utilisez le raccourci suivant:

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

Enum implémentations peut être converti en listes et est itérable. 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

721



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)

298



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

dog, cat, rabbit = range(3)

En Python 3.x vous pouvez également ajouter un espace réservé étoilé à la fin, qui absorbera toutes les valeurs restantes de la gamme dans le cas où cela ne vous dérange pas de gaspiller de la mémoire et ne peut pas compter:

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

183



La meilleure solution pour vous dépend de ce que vous avez besoin de votre faux  enum.

Enum simple:

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

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

Utilisant un range vous permet également de définir valeur de départ:

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

En plus de ce qui précède, si vous exigez également que les articles appartiennent à un récipient de quelque sorte, puis les intégrer 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'enum ou des utilisations plus compliquées d'enum, ces solutions ne suffiront pas. Vous pouvez regarder la recette de Will Ware pour Simulation d'énumérations en Python publié dans le Livre de recettes Python. Une version en ligne de cela est disponible ici.

Plus d'informations:

PEP 354: Enumérations en Python a les détails intéressants d'une proposition pour enum en Python et pourquoi il a été rejeté.


119



Le modèle enum typeafe utilisé dans Java pré-JDK 5 a un nombre d'avantages. Tout comme dans la réponse d'Alexandru, vous créez un les champs de classe et de niveau de classe sont les valeurs enum; cependant, l'énumération les valeurs sont des instances de la classe plutôt que des petits entiers. Cela a l'avantage que vos valeurs enum ne comparent pas par inadvertance égale aux petits entiers, vous pouvez contrôler comment ils sont imprimés, ajouter arbitraire méthodes 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

Une récente le fil sur python-dev a souligné qu'il y a quelques bibliothèques enum dans la nature, y compris:


76



Une classe Enum peut être un one-liner.

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

56



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 sur la version supérieure dans le livre de recettes Python).

Cependant, dans les situations où enum serait appelé en C, je finis habituellement juste en utilisant des chaînes simples: en raison de la façon dont les objets / attributs sont implémentés, (C) Python est optimisé pour fonctionner très rapidement avec des chaînes courtes de toute façon, donc il n'y aurait pas vraiment de bénéfice de performance à utiliser des entiers. Pour vous prémunir contre les fautes de frappe / valeurs invalides, vous pouvez insérer des contrôles dans les 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)


44



Donc, je suis d'accord. N'appliquons pas la sécurité du type en Python, mais je voudrais 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 me garde de la collision de valeur en définissant mes enums.

>>> Animal.Cat
2

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

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

44



Le 2013-05-10, Guido a accepté d'accepter PEP 435 dans la bibliothèque standard Python 3.4. Cela signifie que Python a finalement intégré le support des énumérations!

Il existe un backport 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 par programme:

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

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


30



Je préfère définir enums en Python comme ça:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

C'est plus résistant aux bogues que d'utiliser des entiers puisque vous n'avez pas à vous soucier de vous assurer que les nombres entiers sont uniques (par exemple si vous avez dit Chien = 1 et Cat = 1 vous seriez foutu).

Il est plus résistant aux bugs que l'utilisation de chaînes, car 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).


29