with - python enum34




Wie kann ich ein 'Enum' in Python darstellen? (20)

Ich bin hauptsächlich ein C # -Entwickler, aber ich arbeite gerade an einem Projekt in Python.

Wie kann ich das Äquivalent eines Enums in Python darstellen?


Ab Python 3.4 wird es offizielle Enum-Unterstützung geben. Sie finden Dokumentation und Beispiele hier auf der Python 3.4 Dokumentationsseite .

Aufzählungen werden mit der Klassensyntax erstellt, wodurch sie einfach zu lesen und zu schreiben sind. Eine alternative Erstellungsmethode wird in der funktionalen API beschrieben. Um eine Aufzählung zu definieren, erstellen Sie Enum wie folgt:

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

Alexandrus Vorschlag, Klassenkonstanten für Enums zu verwenden, funktioniert ziemlich gut.

Ich möchte auch ein Wörterbuch für jeden Satz von Konstanten hinzufügen, um eine lesbare Darstellungsform für Menschen zu finden.

Dies dient zwei Zwecken: a) es bietet eine einfache Möglichkeit, Ihre enum hübsch zu drucken und b) das Wörterbuch gruppiert logisch die Konstanten, so dass Sie auf Mitgliedschaft prüfen können.

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

Am 10.05.2013 stimmte Guido zu, PEP 435 in die Python 3.4 Standardbibliothek aufzunehmen. Dies bedeutet, dass Python endlich Unterstützung für Enumerationen eingebaut hat!

Es gibt einen Backport für Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 und 2.4. Es ist auf Pypi als enum34 .

Erklärung:

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

Darstellung:

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

Iteration:

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

Programmatic Zugang:

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

Weitere Informationen finden Sie im Angebot . Die offizielle Dokumentation wird wahrscheinlich bald folgen.


Das Enum-Paket von PyPI bietet eine robuste Implementierung von Enums. Eine frühere Antwort erwähnt PEP 354; Dies wurde abgelehnt, aber der Vorschlag wurde implementiert http://pypi.python.org/pypi/enum .

Die Nutzung ist einfach und elegant:

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

Das typsichere Enum-Muster, das in Java vor JDK 5 verwendet wurde, hat eine Reihe von Vorteilen. Ähnlich wie in Alexandrus Antwort erstellen Sie Klassen- und Klassenebenenfelder, die Enum-Werte. Die enum-Werte sind jedoch Instanzen der Klasse und nicht kleine Ganzzahlen. Dies hat den Vorteil, dass Ihre enum-Werte nicht versehentlich gleich kleine Ganzzahlen vergleichen, Sie können steuern, wie sie gedruckt werden, beliebige Methoden hinzufügen, wenn dies nützlich ist, und Assertionen mit isinstance machen:

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

Ein neuer Thread auf python-dev wies darauf hin, dass es ein paar Enum-Bibliotheken in der Wildnis gibt, darunter:


Der neue Standard in Python ist PEP 435 , daher wird eine Enum-Klasse in zukünftigen Versionen von Python verfügbar sein:

>>> from enum import Enum

Um es jedoch jetzt zu verwenden, können Sie die ursprüngliche Bibliothek installieren, die den PEP motiviert hat:

#sudo pip install flufl.enum   //or #sudo easy_install flufl.enum

Dann können Sie es wie in seinem Online-Guide verwenden :

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

Diese Lösung ist eine einfache Möglichkeit, eine Klasse für die als Liste definierte Enumeration zu erhalten (keine lästigen Integer-Zuweisungen mehr):

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

Eine Enum-Klasse kann ein Einzeiler sein.

class Enum(tuple): __getattr__ = tuple.index

Wie man es benutzt (Vorwärts- und Rückwärtssuche, Schlüssel, Werte, Artikel, 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)]

Enums wurden zu Python 3.4 hinzugefügt, wie in PEP 435 beschrieben . Es wurde auch auf 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 und 2.4 auf pypi zurückportiert.

Für fortgeschrittene Enum-Techniken sollten Sie die Aenum-Bibliothek ausprobieren (2.7, 3.3+, der gleiche Autor wie enum34 . Der Code ist nicht perfekt kompatibel zwischen py2 und py3, zB brauchen Sie __order__ in Python 2 ).

  • Um enum34 , $ pip install enum34
  • Um aenum , $ pip install aenum

Durch die Installation von enum (keine Nummern) wird eine komplett andere und inkompatible Version installiert.

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)

oder gleichwertig:

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

In früheren Versionen war eine Möglichkeit, Enums zu erreichen:

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

das wird so verwendet:

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

Sie können die automatische Aufzählung auch leicht mit etwas wie diesem unterstützen:

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

und so benutzt:

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

Unterstützung für das Zurückkonvertieren der Werte in Namen kann folgendermaßen hinzugefügt werden:

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)

Dies überschreibt alles mit diesem Namen, aber es ist nützlich für das Rendern Ihrer Enums in der Ausgabe. Wenn die umgekehrte Zuordnung nicht vorhanden ist, wird KeyError ausgelöst. Mit dem ersten Beispiel:

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

Halte es einfach:

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

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

Dann:

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

Hier ist eine Variante von Alec Thomas Lösung :

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

Hmmm ... Ich nehme an, ein Enum wäre am ehesten ein Wörterbuch, das so definiert ist:

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

oder

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

Dann können Sie den symbolischen Namen für die Konstanten wie folgt verwenden:

mymonth = months['January']

Es gibt andere Optionen, wie eine Liste von Tupeln oder ein Tupel-Tupel, aber das Wörterbuch ist das einzige, das Ihnen eine "symbolische" (konstante Zeichenkette) Möglichkeit bietet, auf den Wert zuzugreifen.

Edit: Ich mag Alexandrus Antwort auch!


Ich hatte Gelegenheit, eine Enum-Klasse zu benötigen, um ein binäres Dateiformat zu dekodieren. Die Features, die ich gerade wollte, sind eine präzise Enum-Definition, die Möglichkeit, Instanzen der Enumeration entweder mit Ganzzahl oder String zu erstellen, und eine nützliche Repräsentation. Hier ist, womit ich endete:

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

Ein wunderliches Beispiel dafür:

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

Hauptmerkmale:

  • str() , int() und repr() erzeugen alle die nützlichste mögliche Ausgabe bzw. den Namen der Enumeration, ihren Ganzzahlwert und einen Python-Ausdruck, der zurück zur Enumeration ausgewertet wird.
  • Aufzählungswerte, die vom Konstruktor zurückgegeben werden, sind streng auf die vordefinierten Werte beschränkt, keine zufälligen Aufzählungswerte.
  • Aufzählungswerte sind Singletons; Sie können streng mit denen verglichen werden

Ich mag die Lösung von Alec Thomas (http://.com/a/1695250):

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

Es ist elegant und sauber, aber es ist nur eine Funktion, die eine Klasse mit den angegebenen Attributen erstellt.

Mit ein wenig Modifikation der Funktion können wir es ein wenig mehr 'enumy' wirken lassen:

HINWEIS: Ich habe die folgenden Beispiele erstellt, indem ich versucht habe, das Verhalten von pygtk's neuen Stil 'enums' zu reproduzieren (wie 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

Dies erstellt eine Enumeration, die auf einem angegebenen Typ basiert. Zusätzlich zu dem Attributzugriff wie bei der vorherigen Funktion verhält es sich wie erwartet in Bezug auf Typen. Es erbt auch die Basisklasse.

Zum Beispiel ganzzahlige Enums:

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

Eine weitere interessante Sache, die mit dieser Methode durchgeführt werden kann, ist das Anpassen des spezifischen Verhaltens durch Überschreiben von integrierten Methoden:

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'

Vor PEP 435 hatte Python keine Entsprechung, aber Sie konnten Ihre eigenen implementieren.

Ich mag es, es einfach zu halten (ich habe einige schrecklich komplexe Beispiele im Netz gesehen), so etwas ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

In Python 3.4 ( PEP 435 ) können Sie Enum zur Basisklasse machen. Dies bringt Ihnen ein wenig zusätzliche Funktionalität, die im PEP beschrieben wird. Enumerationswerte unterscheiden sich beispielsweise von Ganzzahlen.

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

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

Wenn Sie die Werte nicht eingeben möchten, verwenden Sie die folgende Verknüpfung:

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

Was ich benutze:

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

Wie benutzt man:

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

So erhalten Sie Integer-Konstanten wie state.PUBLISHED und die Zwei-Tupel, die als Auswahlmöglichkeiten in Django-Modellen verwendet werden.


davidg empfiehlt die Verwendung von Diktaten. Ich würde einen Schritt weiter gehen und Sätze verwenden:

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

Jetzt können Sie testen, ob ein Wert mit einem der Werte in der Menge übereinstimmt:

if m in months:

Wie bei dF verwende ich normalerweise nur String-Konstanten anstelle von Enums.


Here's an approach with some different characteristics I find valuable:

  • allows > and < comparison based on order in enum, not lexical order
  • can address item by name, property or index: xa, x['a'] or x[0]
  • supports slicing operations like [:] or [-1]

and most importantly prevents comparisons between enums of different types !

Based closely on http://code.activestate.com/recipes/413486-first-class-enums-in-python .

Many doctests included here to illustrate what's different about this approach.

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

While the original enum proposal, PEP 354 , was rejected years ago, it keeps coming back up. Some kind of enum was intended to be added to 3.2, but it got pushed back to 3.3 and then forgotten. And now there's a PEP 435 intended for inclusion in Python 3.4. The reference implementation of PEP 435 is flufl.enum .

As of April 2013, there seems to be a general consensus that something should be added to the standard library in 3.4—as long as people can agree on what that "something" should be. That's the hard part. See the threads starting here and here , and a half dozen other threads in the early months of 2013.

Meanwhile, every time this comes up, a slew of new designs and implementations appear on PyPI, ActiveState, etc., so if you don't like the FLUFL design, try a PyPI search .


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

Benutze es so:

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

Wenn Sie nur eindeutige Symbole möchten und sich nicht um die Werte kümmern, ersetzen Sie diese Zeile:

__metaclass__ = M_add_class_attribs(enumerate(names))

mit diesem:

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




enums