Wie kann ich eine 'Aufzählung' in Python darstellen?

1143

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

Wie kann ich das Äquivalent einer Aufzählung in Python darstellen?

sectrean
quelle

Antworten:

2688

Python 3.4 wurden Enums 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 zurückportiert zurückportiert.

Versuchen Sie für fortgeschrittenere Enum-Techniken die Aenum-Bibliothek (2.7, 3.3+, derselbe Autor wie enum34. Der Code ist zwischen py2 und py3 nicht perfekt kompatibel, z. B. __order__in Python 2 ).

  • Um zu verwenden enum34, tun$ pip install enum34
  • Um zu verwenden aenum, tun$ pip install aenum

Durch die Installation enum(keine Nummern) wird eine völlig 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 äquivalent:

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

In früheren Versionen ist eine Möglichkeit, Aufzählungen zu erstellen, folgende:

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

was so verwendet wird:

>>> 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 problemlos mit folgenden Funktionen unterstützen:

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

und wie folgt verwendet:

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

Die Unterstützung für die Rückkonvertierung 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, ist jedoch nützlich, um Ihre Aufzählungen in der Ausgabe zu rendern. Es wird KeyError ausgelöst, wenn die umgekehrte Zuordnung nicht vorhanden ist. Mit dem ersten Beispiel:

>>> Numbers.reverse_mapping['three']
'THREE'
Alec Thomas
quelle
1
Ich konnte nicht verstehen, warum sie kwargs (** named) in der Methode enum (* sequential, ** named) übergeben haben. Bitte erklärt. Ohne kwargs wird es auch funktionieren. Ich habe nachgeschaut.
Seenu S
Es wäre schön, die Python 2-Funktion so zu aktualisieren, dass sie mit der funktionalen API von Enum (Name, Werte) von Python 3 kompatibel ist
bscan
Das var kwargs ( **named) in der Enum-Funktion für ältere Versionen soll benutzerdefinierte Werte unterstützen:enum("blue", "red", "green", black=0)
Éric Araujo
823

Vor PEP 435 hatte Python kein Äquivalent, aber Sie konnten Ihr eigenes implementieren.

Ich selbst mag 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. Dadurch erhalten Sie ein wenig zusätzliche Funktionalität, die im PEP beschrieben wird. Beispielsweise unterscheiden sich Enum-Mitglieder von Ganzzahlen und setzen sich aus a nameund a zusammen value.

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

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

print(Animal.DOG.value)
# 1

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

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

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

EnumImplementierungen können in Listen konvertiert werden und sind iterierbar . Die Reihenfolge ihrer Mitglieder ist die Deklarationsreihenfolge und hat nichts mit ihren Werten zu tun. Zum Beispiel:

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
quelle
51
Nein, es ist eine Klassenvariable.
Georg Schölly
246
Python ist standardmäßig dynamisch. Es gibt keinen gültigen Grund, die Sicherheit während der Kompilierung in einer Sprache wie Python zu erzwingen, insbesondere wenn keine vorhanden ist. Und noch etwas ... ein gutes Muster ist nur in dem Kontext gut, in dem es erstellt wurde. Ein gutes Muster kann je nach verwendetem Werkzeug auch ersetzt oder völlig unbrauchbar werden.
Alexandru Nedelcu
20
@Longpoke Wenn Sie 100 Werte haben, dann machen Sie definitiv etwas falsch;) Ich mag Zahlen, die mit meinen Aufzählungen verknüpft sind ... sie sind einfach zu schreiben (im Vergleich zu Zeichenfolgen), können leicht in einer Datenbank gespeichert werden und sind kompatibel mit die C / C ++ - Aufzählung, die das Marshalling erleichtert.
Alexandru Nedelcu
50
Ich benutze dies, wobei die Nummern durch ersetzt werden object().
Tobu
9
Das ursprüngliche PEP354 wird nicht mehr nur abgelehnt, sondern jetzt als ersetzt markiert. PEP435 fügt eine Standard-Enum für Python 3.4 hinzu. Siehe python.org/dev/peps/pep-0435
Peter Hansen
322

Hier ist eine Implementierung:

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

Hier ist seine Verwendung:

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

print(Animals.DOG)
shahjapan
quelle
51
Ausgezeichnet. Dies kann durch Überschreiben weiter verbessert werden, __setattr__(self, name, value)und __delattr__(self, name)wenn Sie versehentlich schreiben Animals.DOG = CAT, ist dies möglicherweise nicht im Hintergrund erfolgreich.
Joonas Pulakka
15
@shahjapan: Interessant, aber relativ langsam: Für jeden Zugriff wird ein Test durchgeführt wie Animals.DOG; Außerdem sind die Werte der Konstanten Zeichenfolgen, sodass Vergleiche mit diesen Konstanten langsamer sind, als wenn beispielsweise Ganzzahlen als Werte zulässig wären.
Eric O Lebigot
3
@shahjapan: Ich würde argumentieren, dass diese Lösung nicht so lesbar ist wie die kürzeren Lösungen von Alexandru oder Mark zum Beispiel. Es ist jedoch eine interessante Lösung. :)
Eric O Lebigot
Ich habe versucht, die setattr()Funktion inside __init__()method zu verwenden, anstatt die Methode zu überschreiben __getattr__(). Ich gehe davon aus, dass dies genauso funktionieren soll: Klasse Enum (Objekt): def __init __ (self, enum_string_list): if type (enum_string_list) == list: für enum_string in enum_string_list: setattr (self, enum_string, enum_string) else: raise AttributeError
Harshith JV
8
@ AndréTerra: Wie prüft man, ob in einem try-exceptBlock eine Mitgliedschaft festgelegt ist ?
Bgusach
210

Wenn Sie die numerischen Werte benötigen, ist dies der schnellste Weg:

dog, cat, rabbit = range(3)

In Python 3.x können Sie am Ende auch einen markierten Platzhalter hinzufügen, der alle verbleibenden Werte des Bereichs aufnimmt, falls Sie nichts dagegen haben, Speicher zu verschwenden, und nicht zählen können:

dog, cat, rabbit, horse, *_ = range(100)
Mark Harrison
quelle
1
Dies könnte jedoch mehr Speicherplatz beanspruchen!
MJ
Ich sehe den Punkt des markierten Platzhalters nicht, da Python die Anzahl der zu entpackenden Werte überprüft (also die Zählung für Sie übernimmt).
Gabriel Devillers
@GabrielDevillers, ich denke, Python wird eine Ausnahme auslösen, wenn die Anzahl der zuzuweisenden Elemente im Tupel nicht übereinstimmt.
Mark Harrison
1
In meinem Test (Python2,3) ist dies zwar der Fall, aber das bedeutet, dass jeder Zählfehler des Programmierers beim ersten Test abgefangen wird (mit einer Meldung, die die richtige Zählung angibt).
Gabriel Devillers
1
Ich kann nicht zählen Kann der markierte Platzhalter auch meine Finanzen reparieren?
Javadba
131

Die beste Lösung für Sie hängt davon ab, was Sie von Ihrer Fälschung benötigen enum.

Einfache Aufzählung:

Wenn Sie enumnur eine Liste von Namen benötigen, die verschiedene Elemente identifizieren , ist die Lösung von Mark Harrison (oben) großartig:

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

Mit a rangekönnen Sie auch einen beliebigen Startwert festlegen :

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

Wenn Sie außerdem benötigen, dass die Elemente zu einem Container gehören , binden Sie sie in eine Klasse ein:

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

Um das Aufzählungselement zu verwenden, müssten Sie jetzt den Containernamen und den Elementnamen verwenden:

stype = Stationery.Pen

Komplexe Aufzählung:

Für lange Listen von Aufzählungen oder kompliziertere Verwendungen von Aufzählungen reichen diese Lösungen nicht aus. Sie können sich das Rezept von Will Ware zur Simulation von Aufzählungen in Python ansehen, das im Python-Kochbuch veröffentlicht wurde . Eine Online-Version davon finden Sie hier .

Mehr Info:

PEP 354: Aufzählungen in Python enthalten die interessanten Details eines Vorschlags für eine Aufzählung in Python und warum er abgelehnt wurde.

Ashwin
quelle
7
mit können rangeSie das erste Argument weglassen, wenn es 0 ist
ToonAlfrink
Eine andere gefälschte Aufzählung, die für einige Zwecke geeignet ist, ist my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Dann my_enumkann bei der Suche verwendet werden, zB my_enum['Item0']kann ein Index in eine Sequenz sein. Möglicherweise möchten Sie das Ergebnis von str.splitin eine Funktion einschließen, die eine Ausnahme auslöst, wenn Duplikate vorhanden sind.
Ana Nimbus
Nett! Für Flaggen können SieFlag1, Flag2, Flag3 = [2**i for i in range(3)]
Majkelx
Dies ist die beste Antwort
Yuriy Pozniak
78

Das typsichere Enum-Muster, das in Java vor JDK 5 verwendet wurde, bietet eine Reihe von Vorteilen. Ähnlich wie in Alexandru's Antwort erstellen Sie eine Klasse und Felder auf Klassenebene sind die Aufzählungswerte. Die Aufzählungswerte sind jedoch eher Instanzen der Klasse als kleine Ganzzahlen. Dies hat den Vorteil, dass Ihre Enum-Werte nicht versehentlich mit kleinen Ganzzahlen verglichen werden. Sie können steuern, wie sie gedruckt werden, beliebige Methoden hinzufügen, wenn dies nützlich ist, und mithilfe von isinstance Aussagen treffen:

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 kürzlich veröffentlichter Thread zu Python-Dev wies darauf hin, dass es einige Enum-Bibliotheken in freier Wildbahn gibt, darunter:

Aaron Maenpaa
quelle
16
Ich denke, das ist ein sehr schlechter Ansatz. Animal.DOG = Tier ("Hund") Animal.DOG2 = Tier ("Hund") behauptet Animal.DOG == Animal.DOG2 schlägt fehl ...
Verwirrung
11
@Confusion Der Benutzer soll den Konstruktor nicht aufrufen, die Tatsache, dass es sogar einen Konstruktor gibt, ist ein Implementierungsdetail, und Sie müssen jedem, der Ihren Code verwendet, mitteilen, dass das Erstellen neuer Aufzählungswerte keinen Sinn macht und das Beenden von Code nicht "tue das Richtige". Das hindert Sie natürlich nicht daran, Animal.from_name ("dog") -> Animal.DOG zu implementieren.
Aaron Maenpaa
13
"Der Vorteil, dass Ihre Enum-Werte nicht versehentlich mit kleinen ganzen Zahlen verglichen werden" Was ist der Vorteil dabei? Was ist falsch daran, deine Aufzählung mit ganzen Zahlen zu vergleichen? Insbesondere wenn Sie die Aufzählung in der Datenbank speichern, möchten Sie normalerweise, dass sie als Ganzzahlen gespeichert wird, sodass Sie sie irgendwann mit Ganzzahlen vergleichen müssen.
ibz
3
@ Aaron Maenpaa. richtig. Es ist immer noch eine kaputte und übermäßig komplizierte Art, dies zu tun.
Aaronasterling
4
@AaronMcSmooth Das hängt wirklich davon ab, ob Sie aus der C-Perspektive von "Enums sind nur Namen für ein paar Ints" oder dem objektorientierteren Ansatz, bei dem Enum-Werte tatsächliche Objekte sind und Methoden haben (wie Enums in Java) 1,5 sind, und für die das typsichere Aufzählungsmuster war). Persönlich mag ich switch-Anweisungen nicht, daher neige ich zu Aufzählungswerten, die tatsächliche Objekte sind.
Aaron Maenpaa
61

Eine Enum-Klasse kann ein Einzeiler sein.

class Enum(tuple): __getattr__ = tuple.index

Verwendung (Vorwärts- und Rückwärtssuche, Schlüssel, Werte, Elemente usw.)

>>> 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
quelle
Ich denke, es ist die einfachste und eleganteste Lösung. In Python 2.4 (ja, alter Legacy-Server) haben Tupel keinen Index. Ich habe das Ersetzen durch Liste gelöst.
Massimo
Ich habe dies in einem Jupyter-Notizbuch versucht und festgestellt, dass es nicht als einzeilige Definition funktioniert, aber dass das Akzeptieren der getattr- Definition in einer zweiten (eingerückten) Zeile akzeptiert wird.
user5920660
Mit dieser Lösung kann ich das inSchlüsselwort verwenden, um nach ordentlichen Mitgliedern zu suchen. Anwendungsbeispiel:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini
51

Also stimme ich zu. Lassen Sie uns die Typensicherheit in Python nicht erzwingen, aber ich möchte mich vor dummen Fehlern schützen. Was denken wir darüber?

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

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

Es hält mich von Wertkollisionen bei der Definition meiner Aufzählungen ab.

>>> Animal.Cat
2

Es gibt noch einen weiteren praktischen Vorteil: wirklich schnelle Reverse-Lookups:

def name_of(self, i):
    return self.values[i]
königlich
quelle
Ich mag das, aber Sie können genauso gut Werte für die Effizienz mit einem Tupel speichern? Ich habe damit herumgespielt und eine Version entwickelt, die self.values ​​von args in init festlegt . Es ist schön, deklarieren zu können Animal = Enum('horse', 'dog', 'cat'). Ich fange auch den ValueError in getattr ab, wenn ein Element in self.values ​​fehlt. Es scheint besser, stattdessen einen AttributeError mit der angegebenen Namenszeichenfolge auszulösen. Aufgrund meiner begrenzten Kenntnisse in diesem Bereich konnte ich die Metaklasse in Python 2.7 nicht zum Laufen bringen, aber meine benutzerdefinierte Enum-Klasse funktioniert problemlos mit direkten Instanzmethoden.
Trojjer
49

Python hat kein eingebautes Äquivalent zu enumund andere Antworten haben Ideen für die Implementierung Ihrer eigenen (Sie könnten auch an der Over-the-Top-Version im Python-Kochbuch interessiert sein ).

In Situationen, in denen ein enumin C erforderlich ist , verwende ich normalerweise nur einfache Zeichenfolgen : Aufgrund der Art und Weise, wie Objekte / Attribute implementiert werden, ist (C) Python so optimiert, dass es ohnehin sehr schnell mit kurzen Zeichenfolgen arbeitet Die Verwendung von Ganzzahlen ist kein wirklicher Leistungsvorteil. Um Tippfehler / ungültige Werte zu vermeiden, können Sie an ausgewählten Stellen Schecks einfügen.

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

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

(Ein Nachteil gegenüber der Verwendung einer Klasse besteht darin, dass Sie den Vorteil der automatischen Vervollständigung verlieren.)

dF.
quelle
2
Ich bevorzuge diese Lösung. Ich verwende gerne eingebaute Typen, wo dies möglich ist.
Seun Osewa
Diese Version ist nicht wirklich übertrieben. Es hat nur eine Menge
Testcode
1
Tatsächlich ist die "richtige" Version in den Kommentaren und viel komplexer - die Hauptversion hat einen kleinen Fehler.
Casebash
39

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 Aufzählungen hat!

Für Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 und 2.4 ist ein Backport verfügbar. 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>

Wiederholung:

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

Programmatischer Zugriff:

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

Weitere Informationen finden Sie im Vorschlag . Offizielle Unterlagen werden voraussichtlich bald folgen.

Danilo Bargen
quelle
33

Ich definiere Enums in Python lieber so:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Es ist fehlerfreier als die Verwendung von Ganzzahlen, da Sie sich nicht darum kümmern müssen, dass die Ganzzahlen eindeutig sind (z. B. wenn Sie Dog = 1 und Cat = 1 sagen, werden Sie geschraubt).

Es ist fehlerfreier als die Verwendung von Zeichenfolgen, da Sie sich keine Gedanken über Tippfehler machen müssen (z. B. x == "catt" schlägt stillschweigend fehl, aber x == Animal.Catt ist eine Laufzeitausnahme).

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

Verwenden Sie 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)
18695
quelle
11
IMHO wäre es sauberer sein , wenn Sie geändert enum(names)zu enum(*names)- dann könnte man die zusätzliche Klammer fallen , wenn Sie anrufen.
Chris Lutz
Ich mag diesen Ansatz. Ich habe es tatsächlich geändert, um den Attributwert auf dieselbe Zeichenfolge wie den Namen zu setzen, die die nette Eigenschaft Animal.DOG == 'DOG' hat, sodass sie sich automatisch für Sie stringifizieren. (Hilft immens beim Ausdrucken der Debug-Ausgabe.)
Ted Mielczarek
23

Ab Python 3.4 wird es offizielle Unterstützung für Aufzählungen geben. Dokumentation und Beispiele finden Sie hier auf der Python 3.4-Dokumentationsseite .

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

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3
Cyril Gandon
quelle
Die Rückportierung wird jetzt ebenfalls unterstützt. Dies ist der richtige Weg.
Srock
22

Hmmm ... Ich nehme an, das, was einer Aufzählung am nächsten kommt, ist ein Wörterbuch, das entweder 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 von Tupeln, aber das Wörterbuch ist die einzige, die Ihnen eine "symbolische" (konstante Zeichenfolge) Möglichkeit bietet, auf den Wert zuzugreifen.

Edit: Ich mag auch Alexandru's Antwort!

Dguaraglia
quelle
Und vor allem können Sie ein Wörterbuch problemlos durchlaufen, wenn Sie auf seine Werte zugreifen müssen, so wie Sie möchten, dass seine Zeichenfolgenwerte als Kombinationsfeldelemente angezeigt werden. Verwenden Sie stattdessen ein Wörterbuch als Ersatz für Aufzählungen.
LEMUEL ADANE
22

Eine weitere, sehr einfache Implementierung einer Enumeration in Python mit namedtuple:

from collections import namedtuple

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

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

oder alternativ,

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

setDies ermöglicht wie die obige Methode, die Unterklassen umfasst , Folgendes:

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

Hat aber mehr Flexibilität, da es unterschiedliche Schlüssel und Werte haben kann. Dies erlaubt

MyEnum.FOO < MyEnum.BAR

um wie erwartet zu handeln, wenn Sie die Version verwenden, die fortlaufende Zahlenwerte ausfüllt.

agf
quelle
20

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

Dies gibt Ihnen also ganzzahlige Konstanten wie state.PUBLISHED und die zwei Tupel, die Sie in Django-Modellen als Auswahl verwenden können.

Luciano Ramalho
quelle
17

Davidg empfiehlt die Verwendung von Diktaten. Ich würde noch einen Schritt weiter gehen und Sets verwenden:

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

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

if m in months:

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

Smoking
quelle
yep!, viel besser, wenn du set erbst und die getattr- Methode bereitstellst !
Shahjapan
17

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
Gefahr89
quelle
16

Dies ist die beste, die ich je gesehen habe: "First Class Enums in Python"

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

Es gibt Ihnen eine Klasse, und die Klasse enthält alle Aufzählungen. Die Aufzählungen können miteinander verglichen werden, haben jedoch keinen bestimmten Wert. Sie können sie nicht als ganzzahligen Wert verwenden. (Ich habe mich zunächst dagegen gewehrt, weil ich an C-Enums gewöhnt bin, bei denen es sich um ganzzahlige Werte handelt. Wenn Sie es jedoch nicht als Ganzzahl verwenden können, können Sie es nicht versehentlich als Ganzzahl verwenden. Insgesamt denke ich, dass es ein Gewinn ist .) Jede Aufzählung ist ein eindeutiger Wert. Sie können Aufzählungen drucken, Sie können darüber iterieren, Sie können testen, ob ein Aufzählungswert "in" der Aufzählung ist. Es ist ziemlich vollständig und glatt.

Bearbeiten (cfi): Der obige Link ist nicht Python 3-kompatibel. Hier ist meine Portierung von enum.py zu 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
quelle
Dieses Rezept wurde als Grundlage für ein PEP verwendet, das abgelehnt wurde. python.org/dev/peps/pep-0354 Eine Erweiterung, die mir gefällt: Aufzählungswerte sollten eine Mitgliedsvariable haben, mit der Sie den internen ganzzahligen Wert ermitteln können. Es sollte nicht möglich sein, versehentlich eine Aufzählung in eine Ganzzahl umzuwandeln, daher sollte die .__int__()Methode eine Ausnahme für eine Aufzählung auslösen. Aber es sollte einen Weg geben, den Wert herauszuholen. Und es sollte möglich sein, bestimmte ganzzahlige Werte zur Zeit der Klassendefinition festzulegen, damit Sie eine Aufzählung für Dinge wie die Konstanten im statModul verwenden können.
Steveha
14

Ich hatte Gelegenheit, eine Enum-Klasse zu benötigen, um ein binäres Dateiformat zu dekodieren. Die Funktionen, die ich zufällig haben wollte, sind eine reprpräzise Aufzählungsdefinition, die Möglichkeit, Instanzen der Aufzählung entweder durch einen ganzzahligen Wert oder eine Zeichenfolge frei zu erstellen, und eine nützliche Darstellung. Folgendes habe ich erreicht:

>>> 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 für die Verwendung:

>>> 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()undrepr() alle erzeugen die nützlichsten Ausgabe möglich, jeweils der Name des enumartion, dessen ganzzahliger Wert und einem Ausdruck, Python auswertet auf die Aufzählung zurück.
  • Vom Konstruktor zurückgegebene Aufzählungswerte sind streng auf die vordefinierten Werte beschränkt, keine zufälligen Aufzählungswerte.
  • Aufzählungswerte sind Singletons; Sie können streng mit verglichen werdenis
TokenMacGuy
quelle
Ich mag die Verwendung einer Superklasse mit einer eigenen Metaklasse sehr, um das Definieren von Aufzählungen zu vereinfachen. Was hier fehlt, ist eine __contains__- Methode. Ich möchte überprüfen können, ob eine bestimmte Variable Teil der Aufzählung ist - hauptsächlich, weil ich die Aufzählungen für zulässige Werte eines Funktionsparameters haben möchte.
Xorsyst
Dies ist eine leicht gekürzte Version der ursprünglich erstellten Version (die Sie hier finden: enum_strict.py ) v, die eine __instancecheck__Methode definiert . Klassen sind keine Sammlungen von Instanzen, daher 1 in Fruitist es absurd. Die verknüpfte Version unterstützt jedoch, isinstance(1, Fruit)was in Bezug auf den Begriff der Klassen und Instanzen korrekter wäre.
SingleNegationElimination
Aber wenn man Klassen vergisst und in Aufzählungen denkt, ist es sinnvoll, sie als Sammlung zu betrachten. Zum Beispiel könnte ich eine Aufzählung von Dateieröffnungsmodi haben (MODE.OPEN, MODE.WRITE usw.). Ich möchte die Parameter für meine Funktion überprüfen: wenn Modus in MODE: liest viel besser als isintance (Modus, Modus)
Xorsyst
Ich habe etwas sehr Ähnliches auf GitHub veröffentlicht, das mehr als nur Ints unterstützt und dokumentiert und getestet ist: github.com/hmeine/named_constants
hans_meine
12

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

>>> from enum import Enum

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

$ pip install flufl.enum

Dann Sie es gemäß seiner Online-Anleitung 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
Riaz Rizvi
quelle
10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Wenn Sie es benennen, ist dies Ihr Problem. Wenn Sie jedoch keine Objekte anstelle von Werten erstellen, können Sie Folgendes tun:

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

Wenn Sie andere hier angeordnete Implementierungen verwenden (in meinem Beispiel auch benannte Instanzen), müssen Sie sicherstellen, dass Sie niemals versuchen, Objekte aus verschiedenen Aufzählungen zu vergleichen. Denn hier ist eine mögliche Falle:

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

Huch!

estani
quelle
9

Die Lösung von Alec Thomas (http://stackoverflow.com/a/1695250) gefällt mir sehr gut:

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

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

Mit einer kleinen Modifikation der Funktion können wir sie ein wenig "enumy" wirken lassen:

HINWEIS: Ich habe die folgenden Beispiele erstellt, indem ich versucht habe, das Verhalten der neuen Stil-Enums von pygtk (wie Gtk.MessageType.WARNING) zu reproduzieren.

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

Dadurch wird eine Aufzählung basierend auf einem angegebenen Typ erstellt. Zusätzlich zum Gewähren des Attributzugriffs wie bei der vorherigen Funktion verhält es sich so, wie Sie es von einer Aufzählung in Bezug auf Typen erwarten würden. Es erbt auch die Basisklasse.

Zum Beispiel ganzzahlige Aufzählungen:

>>> 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 eines bestimmten Verhaltens durch Überschreiben der 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'
bj0
quelle
Diese Idee vom Typ "Basis" ist ordentlich :)
MestreLion
Ja, beachten Sie, dass Sie dies auch mit der neuen Python 3.4- Enumeration
bj0
7

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

Die Verwendung 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'
Ashwin
quelle
5

Alexandru's Vorschlag, Klassenkonstanten für Enums zu verwenden, funktioniert ganz gut.

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

Dies dient zwei Zwecken: a) Es bietet eine einfache Möglichkeit, Ihre Aufzählung hübsch auszudrucken, und b) das Wörterbuch gruppiert die Konstanten logisch, damit Sie die Mitgliedschaft testen 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())
Rick Harris
quelle
5

Hier ist ein Ansatz mit verschiedenen Merkmalen, die ich wertvoll finde:

  • erlaubt> und <Vergleich basierend auf der Reihenfolge in enum, nicht lexikalischer Reihenfolge
  • kann das Element nach Name, Eigenschaft oder Index adressieren: xa, x ['a'] oder x [0]
  • unterstützt Slicing-Operationen wie [:] oder [-1]

und verhindert vor allem Vergleiche zwischen Aufzählungen verschiedener Typen !

Basierend auf http://code.activestate.com/recipes/413486-first-class-enums-in-python .

Viele hier enthaltene Doktrinen veranschaulichen, was an diesem Ansatz anders ist.

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

Hier ist eine Variante der Lösung von 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
quelle
3

Diese Lösung ist eine einfache Möglichkeit, eine Klasse für die als Liste definierte Aufzählung abzurufen (keine lästigen Ganzzahlzuweisungen 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',
))
Michael Truog
quelle
2
Dies ist eine sehr alte Methode, um Klassen zu erstellen. Warum nicht einfach type(class_name, (object,), dict(...))stattdessen verwenden?
Endstation
3

Während der ursprüngliche Enum-Vorschlag, PEP 354 , vor Jahren abgelehnt wurde, kommt er immer wieder auf. Eine Art Aufzählung sollte zu 3.2 hinzugefügt werden, aber sie wurde auf 3.3 zurückgeschoben und dann vergessen. Und jetzt gibt es einen PEP 435 , der für die Aufnahme in Python 3.4 vorgesehen ist. Die Referenzimplementierung von PEP 435 istflufl.enum .

Ab April 2013 scheint es einen allgemeinen Konsens zu geben, dass der Standardbibliothek in 3.4 etwas hinzugefügt werden sollte - solange sich die Leute darauf einigen können, was dieses "Etwas" sein soll. Das ist der schwierige Teil. Siehe die Threads, die hier und hier beginnen , und ein halbes Dutzend anderer Threads in den ersten Monaten des Jahres 2013.

In der Zwischenzeit werden jedes Mal eine Reihe neuer Designs und Implementierungen auf PyPI, ActiveState usw. angezeigt. Wenn Ihnen das FLUFL-Design nicht gefällt, versuchen Sie es mit einer PyPI-Suche .

abarnert
quelle
3

Verwenden Sie Folgendes.

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

Ich habe das für die Auswahl des Django- Modells verwendet und es sieht sehr pythonisch aus. Es ist nicht wirklich eine Aufzählung, aber es macht den Job.

Natim
quelle