Domanda

Sono principalmente uno sviluppatore C#, ma attualmente sto lavorando a un progetto in Python.

Come posso rappresentare l'equivalente di un Enum in Python?

È stato utile?

Soluzione

Le enumerazioni sono state aggiunte a Python 3.4 come descritto in PEP 435.Lo è stato anche backport a 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4 su Pypi.

Per tecniche Enum più avanzate prova il file biblioteca dell'aenum (2.7, 3.3+, stesso autore di enum34.Il codice non è perfettamente compatibile tra py2 e py3, ad es.avrai bisogno __order__ in Python 2).

  • Usare enum34, Fare $ pip install enum34
  • Usare aenum, Fare $ pip install aenum

Installazione enum (nessun numero) installerà una versione completamente diversa e incompatibile.


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)

o equivalentemente:

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

Nelle versioni precedenti, un modo per eseguire le enumerazioni è:

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

che viene utilizzato in questo modo:

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

Puoi anche supportare facilmente l'enumerazione automatica con qualcosa del genere:

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

e usato in questo modo:

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

Il supporto per riconvertire i valori in nomi può essere aggiunto in questo modo:

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)

Questo sovrascrive qualsiasi cosa con quel nome, ma è utile per visualizzare le tue enumerazioni nell'output.Lancerà KeyError se la mappatura inversa non esiste.Con il primo esempio:

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

Altri suggerimenti

Prima del PEP 435, Python non aveva un equivalente ma potevi implementarne uno tuo.

Per quanto mi riguarda, mi piace mantenerlo semplice (ho visto alcuni esempi orribilmente complessi in rete), qualcosa del genere...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

In Python 3.4 (PEP 435), tu puoi fare Enum la classe base.Questo ti offre alcune funzionalità extra, descritte nel PEP.Ad esempio, i membri enum sono distinti dagli interi e sono composti da a name e 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"

Se non vuoi digitare i valori, usa la seguente scorciatoia:

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

Enum implementazioni possono essere convertiti in elenchi e sono iterabili.L'ordine dei suoi membri è l'ordine di dichiarazione e non ha nulla a che fare con i loro valori.Per esempio:

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

Ecco un'implementazione:

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

Ecco il suo utilizzo:

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

print(Animals.DOG)

Se hai bisogno dei valori numerici, ecco il modo più rapido:

dog, cat, rabbit = range(3)

In Python 3.x puoi anche aggiungere un segnaposto con stella alla fine, che assorbirà tutti i valori rimanenti dell'intervallo nel caso in cui non ti dispiaccia sprecare memoria e non puoi contare:

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

La soluzione migliore per te dipende da ciò che richiedi dal tuo impostore enum.

Enumerazione semplice:

Se hai bisogno di enum come solo un elenco di nomi identificandosi diversamente elementi, la soluzione di Marco Harrison (sopra) è fantastico:

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

Usare un range consente inoltre di impostarne qualsiasi valore iniziale:

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

In aggiunta a quanto sopra, se richiedi anche che gli articoli appartengano a a contenitore di qualche tipo, quindi incorporarli in una classe:

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

Per utilizzare l'elemento enum, ora dovresti utilizzare il nome del contenitore e il nome dell'elemento:

stype = Stationery.Pen

Enumerazione complessa:

Per lunghi elenchi di enum o usi più complicati di enum, queste soluzioni non saranno sufficienti.Potresti guardare la ricetta di Will Ware Simulazione di enumerazioni in Python pubblicato nel Libro di cucina Python.È disponibile una versione online Qui.

Ulteriori informazioni:

PEP354:Enumerazioni in Python contiene i dettagli interessanti di una proposta per l'enumerazione in Python e il motivo per cui è stata respinta.

Il modello di enum tysafe che è stato utilizzato in java pre-jdk 5 presenta una serie di vantaggi.Proprio come nella risposta di Alexandru, crei che i campi a livello di classe e classe sono i valori enum;Tuttavia, i valori ENUM sono istanze della classe piuttosto che piccoli numeri interi.Ciò ha il vantaggio che i tuoi valori enum non si confrontano inavvertitamente uguali ai piccoli numeri interi, puoi controllare il modo in cui vengono stampati, aggiungere metodi arbitrari se questo è utile e fare affermazioni usando 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 recente discussione su python-dev ho sottolineato che ci sono un paio di librerie enum in circolazione, tra cui:

Una classe Enum può essere una riga.

class Enum(tuple): __getattr__ = tuple.index

Come usarlo (ricerca diretta e inversa, chiavi, valori, elementi, ecc.)

>>> 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 non ha un equivalente integrato di enum, e altre risposte contengono idee per implementare le tue (potresti essere interessato anche a sopra la versione superiore nel libro di ricette Python).

Tuttavia, nelle situazioni in cui un enum verrebbe richiesto in C, di solito finisco usando semplicemente stringhe semplici:a causa del modo in cui sono implementati gli oggetti/attributi, (C)Python è ottimizzato per funzionare comunque molto velocemente con stringhe brevi, quindi non ci sarebbe alcun vantaggio in termini di prestazioni nell'uso di numeri interi.Per proteggersi da errori di battitura/valori non validi è possibile inserire controlli nei punti selezionati.

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

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

(Uno svantaggio rispetto all'utilizzo di una classe è che si perde il vantaggio del completamento automatico)

Quindi sono d'accordo.Non applichiamo la sicurezza dei tipi in Python, ma vorrei proteggermi da errori stupidi.Quindi cosa ne pensiamo?

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

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

Mi impedisce di entrare in collisione tra valori nel definire le mie enumerazioni.

>>> Animal.Cat
2

C'è un altro vantaggio utile:ricerche inverse davvero veloci:

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

Il 10-05-2013 Guido ha accettato di accettare PEP 435 nella libreria standard Python 3.4.Ciò significa che Python ha finalmente il supporto integrato per le enumerazioni!

È disponibile un backport per Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 e 2.4.È su Pypi come enum34.

Dichiarazione:

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

Rappresentazione:

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

Iterazione:

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

Accesso programmatico:

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

Per ulteriori informazioni, fare riferimento a la proposta.Probabilmente seguirà presto la documentazione ufficiale.

Preferisco definire le enumerazioni in Python in questo modo:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

È più a prova di bug rispetto all'utilizzo di numeri interi poiché non devi preoccuparti di garantire che i numeri interi siano univoci (ad es.se dicessi Cane = 1 e Gatto = 1 saresti fregato).

È più a prova di bug rispetto all'uso delle stringhe poiché non devi preoccuparti di errori di battitura (ad es.x == "catt" fallisce silenziosamente, ma x == Animal.Catt è un'eccezione di runtime).

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

Usalo in questo modo:

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

se vuoi solo simboli univoci e non ti interessano i valori, sostituisci questa riga:

__metaclass__ = M_add_class_attribs(enumerate(names))

con questo:

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

Hmm...Suppongo che la cosa più vicina a un'enumerazione sarebbe un dizionario, definito in questo modo:

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

O

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

Quindi, puoi utilizzare il nome simbolico per le costanti in questo modo:

mymonth = months['January']

Esistono altre opzioni, come un elenco di tuple o una tupla di tuple, ma il dizionario è l'unico che ti fornisce un modo "simbolico" (stringa costante) per accedere al valore.

Modificare:Mi piace anche la risposta di Alexandru!

Un'altra implementazione molto semplice di un'enumerazione in Python, utilizzando namedtuple:

from collections import namedtuple

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

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

o, in alternativa,

# 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())

Come il metodo sopra quello delle sottoclassi set, questo permette:

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

Ma ha maggiore flessibilità in quanto può avere chiavi e valori diversi.Questo permette

MyEnum.FOO < MyEnum.BAR

per agire come previsto se si utilizza la versione che inserisce valori numerici sequenziali.

Cosa uso:

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

Come usare:

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

Quindi questo ti dà costanti intere come state.PUBLISHED e le due tuple da usare come scelte nei modelli Django.

Da Python 3.4 ci sarà il supporto ufficiale per le enumerazioni.Puoi trovare documentazione ed esempi qui nella pagina della documentazione di Python 3.4.

Le enumerazioni vengono create utilizzando la sintassi della classe, il che le rende facili da leggere e scrivere.Un metodo di creazione alternativo è descritto nell'API funzionale.Per definire un'enumerazione, sottoclasse Enum come segue:

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

davidg consiglia di utilizzare dicts.Farei un ulteriore passo avanti e utilizzerei i set:

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

Ora puoi verificare se un valore corrisponde a uno dei valori nel set in questo modo:

if m in months:

come dF, però, di solito utilizzo solo costanti di stringa al posto delle enumerazioni.

Questo è il migliore che abbia mai visto:"Enumerazioni di prima classe in Python"

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

Ti dà una classe e la classe contiene tutte le enumerazioni.Le enumerazioni possono essere paragonate tra loro, ma non hanno alcun valore particolare;non puoi usarli come valore intero.(All'inizio ho resistito perché sono abituato alle enumerazioni C, che sono valori interi.Ma se non puoi usarlo come numero intero, non puoi usarlo come numero intero per errore, quindi nel complesso penso che sia una vittoria.) Ogni enum è un valore univoco.Puoi stampare enumerazioni, puoi scorrere su di esse, puoi verificare che un valore enum sia "dentro" l'enumerazione.È abbastanza completo e fluido.

Modifica (CFI):Il collegamento sopra non è compatibile con Python 3.Ecco il mio port di enum.py su 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)

Mantienilo semplice:

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

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

Poi:

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

Ho avuto occasione di aver bisogno di una classe Enum, allo scopo di decodificare un formato di file binario.Le funzionalità che desideravo sono una definizione concisa dell'enumerazione, la possibilità di creare liberamente istanze dell'enumerazione tramite valore intero o stringa e un'utile representazione.Ecco cosa ho ottenuto:

>>> 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 esempio stravagante di utilizzo:

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

Caratteristiche principali:

  • str(), int() E repr() tutti producono l'output più utile possibile, rispettivamente il nome dell'enumerazione, il suo valore intero e un'espressione Python che restituisce l'enumerazione.
  • I valori enumerati restituiti dal costruttore sono limitati rigorosamente ai valori predefiniti, senza valori enumerativi accidentali.
  • I valori enumerati sono singleton;possono essere strettamente confrontati con is

Mi piace molto la soluzione di Alec Thomas (http://stackoverflow.com/a/1695250):

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

Ha un aspetto elegante e pulito, ma è solo una funzione che crea una classe con gli attributi specificati.

Con una piccola modifica alla funzione, possiamo farla agire in modo un po' più 'enumy':

NOTA:Ho creato i seguenti esempi cercando di riprodurre il comportamento del nuovo stile di Pygtk "Enums" (come 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

Questo crea un'enumerazione basata su un tipo specificato.Oltre a fornire l'accesso agli attributi come la funzione precedente, si comporta come ci si aspetterebbe da un Enum rispetto ai tipi.Eredita anche la classe base.

Ad esempio, enumerazioni intere:

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

Un'altra cosa interessante che può essere fatta con questo metodo è personalizzare un comportamento specifico sovrascrivendo i metodi integrati:

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)

Se lo chiami, è un tuo problema, ma in caso contrario la creazione di oggetti anziché valori ti consente di farlo:

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

Quando usi altre implementazioni qui (anche quando usi istanze denominate nel mio esempio) devi essere sicuro di non provare mai a confrontare oggetti di enumerazioni diverse.Perché ecco una possibile trappola:

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

Cavolo!

Il nuovo standard in Python è PEP 435, quindi una classe Enum sarà disponibile nelle future versioni di Python:

>>> from enum import Enum

Tuttavia per iniziare a usarlo ora puoi installare il file biblioteca originaria che ha motivato il PEP:

$ pip install flufl.enum

Allora lei può usarlo secondo la sua guida online:

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

Il pacchetto enum da PyPI fornisce una solida implementazione delle enumerazioni.Una risposta precedente menzionava PEP 354;questo è stato respinto ma la proposta è stata implementatahttp://pypi.python.org/pypi/enum.

L'utilizzo è semplice ed elegante:

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

Il suggerimento di Alexandru di utilizzare costanti di classe per le enumerazioni funziona abbastanza bene.

Mi piace anche aggiungere un dizionario per ogni serie di costanti per cercare una rappresentazione di stringa leggibile dall'uomo.

Questo ha due scopi:a) fornisce un modo semplice per stampare in modo grazioso la tua enumerazione e b) il dizionario raggruppa logicamente le costanti in modo da poter verificare l'appartenenza.

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

Ecco un approccio con alcune caratteristiche diverse che trovo utili:

  • consente il confronto > e < in base all'ordine nell'enumerazione, non all'ordine lessicale
  • può indirizzare l'elemento per nome, proprietà o indice:x.a, x['a'] o x[0]
  • supporta operazioni di suddivisione come [:] o [-1]

e, soprattutto impedisce i confronti tra enumerazioni di tipo diverso!

Basato strettamente su http://code.activestate.com/recipes/413486-first-class-enums-in-python.

Molti doctest inclusi qui per illustrare le differenze in questo approccio.

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

Ecco una variante su La soluzione di 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

Questa soluzione è un modo semplice per ottenere una classe per l'enumerazione definita come elenco (niente più fastidiose assegnazioni di numeri interi):

enumerazione.py:

import new

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

esempio.py:

import enumeration

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

Mentre la proposta di enumerazione originale, PEP 354, è stato rifiutato anni fa, continua a ripresentarsi.Si intendeva aggiungere una sorta di enumerazione alla 3.2, ma è stata spostata alla 3.3 e poi dimenticata.E ora c'è un PEP 435 destinato all'inclusione in Python 3.4.L'implementazione di riferimento del PEP 435 è flufl.enum.

Ad aprile 2013, sembra esserci un consenso generale su questo qualcosa dovrebbe essere aggiunto alla libreria standard nella versione 3.4, purché le persone siano d'accordo su cosa dovrebbe essere quel "qualcosa".Questa è la parte difficile.Guarda i thread che iniziano Qui E Qui, e una mezza dozzina di altri thread nei primi mesi del 2013.

Nel frattempo, ogni volta che si presenta questo problema, vengono visualizzati una serie di nuovi design e implementazioni su PyPI, ActiveState, ecc., quindi se non ti piace il design FLUFL, prova un Ricerca PyPI.

Una variante (con supporto per ottenere il nome di un valore enum) a La chiara risposta di 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']
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top