Enum-HOWTO

Ein Enum ist eine Menge symbolischer Namen, die an eindeutige Werte gebunden sind. Sie sind globalen Variablen ähnlich, bieten aber ein nützlicheres repr(), Gruppierung, Typsicherheit und ein paar weitere Eigenschaften.

Am nützlichsten sind sie, wenn du eine Variable hast, die einen aus einer begrenzten Auswahl von Werten annehmen kann. Zum Beispiel die Wochentage:

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

Oder vielleicht die RGB-Grundfarben:

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

Wie du siehst, ist ein Enum zu erzeugen so einfach wie eine Klasse zu schreiben, die von Enum selbst erbt.

Bemerkung

Schreibweise von Enum-Mitgliedern

Weil Enums Konstanten darstellen und weil es Probleme mit Namenskollisionen zwischen Methoden und Attributen von Mixin-Klassen und Enum-Namen vermeiden hilft, empfehlen wir dringend GROSSBUCHSTABEN-Namen für Mitglieder und verwenden diesen Stil in unseren Beispielen.

Je nach Art des Enums kann der Wert eines Mitglieds wichtig sein oder nicht, aber in beiden Fällen lässt sich dieser Wert verwenden, um das zugehörige Mitglied zu bekommen:

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

Wie du siehst, zeigt das repr() eines Mitglieds den Enum-Namen, den Mitgliedsnamen und den Wert. Das str() eines Mitglieds zeigt nur den Enum-Namen und den Mitgliedsnamen:

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

Der Typ eines Aufzählungsmitglieds ist das Enum, zu dem es gehört:

>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

Enum-Mitglieder haben ein Attribut, das nur ihren name enthält:

>>> print(Weekday.TUESDAY.name)
TUESDAY

Ebenso haben sie ein Attribut für ihren value:

>>> Weekday.WEDNESDAY.value
3

Anders als viele Sprachen, die Aufzählungen ausschließlich als Name-Wert-Paare behandeln, können Python-Enums Verhalten dazubekommen. So hat datetime.date zum Beispiel zwei Methoden, die den Wochentag zurückgeben: weekday() und isoweekday(). Der Unterschied ist, dass die eine von 0 bis 6 und die andere von 1 bis 7 zählt. Anstatt das selbst im Kopf zu behalten, können wir dem Enum Weekday eine Methode hinzufügen, die den Tag aus der date-Instanz zieht und das passende Enum-Mitglied zurückgibt:

@classmethod
def from_date(cls, date):
    return cls(date.isoweekday())

Das vollständige Enum Weekday sieht nun so aus:

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

Jetzt können wir herausfinden, welcher Tag heute ist! Sieh her:

>>> import datetime as dt
>>> Weekday.from_date(dt.date.today())
<Weekday.TUESDAY: 2>

Wenn du das natürlich an einem anderen Tag liest, siehst du stattdessen diesen Tag.

Dieses Enum Weekday ist großartig, wenn unsere Variable nur einen Tag braucht – aber was, wenn wir mehrere brauchen? Vielleicht schreiben wir eine Funktion, die Aufgaben über eine Woche verteilt, und wollen keine list verwenden – wir könnten eine andere Art von Enum nehmen:

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

Wir haben zwei Dinge geändert: Wir erben von Flag, und die Werte sind alle Potenzen von 2.

Genau wie beim ursprünglichen Enum Weekday oben können wir eine einzelne Auswahl treffen:

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

Aber Flag erlaubt es uns außerdem, mehrere Mitglieder in einer einzigen Variablen zu verbinden:

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

Du kannst über eine Flag-Variable sogar iterieren:

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

Gut, richten wir ein paar Aufgaben ein:

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

Und eine Funktion, die die Aufgaben für einen gegebenen Tag anzeigt:

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

In Fällen, in denen die tatsächlichen Werte der Mitglieder keine Rolle spielen, kannst du dir Arbeit sparen und für die Werte auto() verwenden:

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

Programmgesteuerter Zugriff auf Aufzählungsmitglieder und ihre Attribute

Manchmal ist es nützlich, auf Mitglieder von Aufzählungen programmgesteuert zuzugreifen (also in Lagen, in denen Color.RED nicht in Frage kommt, weil die genaue Farbe zum Zeitpunkt des Programmierens nicht bekannt ist). Enum erlaubt solchen Zugriff:

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

Wenn du über den Namen auf Enum-Mitglieder zugreifen willst, verwende den Zugriff per Index:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

Wenn du ein Enum-Mitglied hast und seinen name oder value brauchst:

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

Enum-Mitglieder und -Werte doppeln

Zwei Enum-Mitglieder mit demselben Namen zu haben ist unzulässig:

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2

Einem Enum-Mitglied können aber andere Namen zugeordnet sein. Gegeben seien zwei Einträge A und B mit demselben Wert (und A zuerst definiert). Dann ist B ein Alias für das Mitglied A. Ein Nachschlagen des Werts von A über den Wert gibt das Mitglied A zurück. Ein Nachschlagen von A über den Namen gibt das Mitglied A zurück. Ein Nachschlagen von B über den Namen gibt ebenfalls das Mitglied A zurück:

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

Bemerkung

Zu versuchen, ein Mitglied mit demselben Namen wie ein bereits definiertes Attribut (ein anderes Mitglied, eine Methode usw.) zu erzeugen, oder ein Attribut mit demselben Namen wie ein Mitglied zu erzeugen, ist nicht erlaubt.

Eindeutige Aufzählungswerte sicherstellen

Standardmäßig erlauben Aufzählungen mehrere Namen als Aliase für denselben Wert. Ist dieses Verhalten nicht gewünscht, kannst du den Dekorator @unique verwenden:

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

Automatische Werte verwenden

Ist der genaue Wert unwichtig, kannst du auto verwenden:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]

Die Werte werden von _generate_next_value_() gewählt, das sich überschreiben lässt:

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

Bemerkung

Die Methode _generate_next_value_() muss vor allen Mitgliedern definiert werden.

Iteration

Beim Iterieren über die Mitglieder eines Enums kommen die Aliase nicht vor:

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

Beachte, dass die Aliase Shape.ALIAS_FOR_SQUARE und Weekday.WEEKEND nicht gezeigt werden.

Das besondere Attribut __members__ ist eine nur lesbare, geordnete Abbildung von Namen auf Mitglieder. Es umfasst alle in der Aufzählung definierten Namen, auch die Aliase:

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

Das Attribut __members__ lässt sich für einen ausführlichen programmgesteuerten Zugriff auf die Aufzählungsmitglieder verwenden – zum Beispiel, um alle Aliase zu finden:

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

Bemerkung

Zu den Aliasen bei Flags gehören Werte mit mehreren gesetzten Flags, etwa 3, und mit keinem gesetzten Flag, also 0.

Vergleiche

Aufzählungsmitglieder werden über die Identität verglichen:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

Ordnende Vergleiche zwischen Aufzählungswerten werden nicht unterstützt. Enum-Mitglieder sind keine Ganzzahlen (siehe aber IntEnum unten):

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

Gleichheitsvergleiche sind allerdings definiert:

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

Gleichheitsvergleiche mit Werten, die keine Aufzählungen sind, geben immer False zurück (auch hier gilt: IntEnum wurde ausdrücklich dafür entworfen, sich anders zu verhalten, siehe unten):

>>> Color.BLUE == 2
False

Warnung

Es ist möglich, Module neu zu laden – enthält ein neu geladenes Modul Enums, werden sie neu erzeugt, und die neuen Mitglieder sind möglicherweise nicht identisch beziehungsweise gleich zu den ursprünglichen Mitgliedern.

Erlaubte Mitglieder und Attribute von Aufzählungen

Die meisten Beispiele oben verwenden Ganzzahlen als Aufzählungswerte. Ganzzahlen zu verwenden ist kurz und praktisch (und wird von der Functional API standardmäßig geliefert), ist aber nicht streng vorgeschrieben. In der weit überwiegenden Zahl der Anwendungsfälle ist es einem gleichgültig, welchen Wert eine Aufzählung tatsächlich hat. Ist der Wert aber wichtig, können Aufzählungen beliebige Werte haben.

Aufzählungen sind Python-Klassen und können wie üblich Methoden und besondere Methoden haben. Wenn wir diese Aufzählung haben:

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

Dann:

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

Die Regeln für das Erlaubte lauten: Namen, die mit einem einzelnen Unterstrich beginnen und enden, sind für enum reserviert und dürfen nicht verwendet werden. Alle anderen innerhalb einer Aufzählung definierten Attribute werden zu Mitgliedern dieser Aufzählung – mit Ausnahme besonderer Methoden (__str__(), __add__() usw.), von Deskriptoren (Methoden sind auch Deskriptoren) und von Variablennamen, die in _ignore_ aufgeführt sind.

Beachte: Definiert deine Aufzählung __new__() und/oder __init__(), werden alle Werte, die dem Enum-Mitglied gegeben werden, an diese Methoden übergeben. Ein Beispiel dazu ist Planet.

Bemerkung

Die Methode __new__() wird, falls definiert, beim Erzeugen der Enum-Mitglieder verwendet. Sie wird dann durch das __new__() von Enum ersetzt, das nach der Klassenerzeugung zum Nachschlagen bestehender Mitglieder dient. Näheres steht unter Wann __new__() und wann __init__().

Eingeschränkte Ableitung von Enum

Eine neue Enum-Klasse muss eine Basis-Enum-Klasse haben, höchstens einen konkreten Datentyp und so viele auf object beruhende Mixin-Klassen wie nötig. Die Reihenfolge dieser Basisklassen ist:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Außerdem ist es nur dann erlaubt, von einer Aufzählung abzuleiten, wenn die Aufzählung keine Mitglieder definiert. Das hier ist also verboten:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

Das hier ist aber erlaubt:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

Die Ableitung von Enums zuzulassen, die Mitglieder definieren, würde wichtige Invarianten von Typen und Instanzen verletzen. Andererseits ist es sinnvoll, gemeinsames Verhalten innerhalb einer Gruppe von Aufzählungen zu teilen. (Ein Beispiel dazu ist OrderedEnum.)

Unterstützung für Dataclasses

Beim Erben von einer dataclass lässt __repr__() den Namen der geerbten Klasse weg. Zum Beispiel:

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

Verwende das Argument repr=False von dataclass(), um das übliche repr() zu nutzen.

Geändert in Version 3.12: Im Wertebereich werden nur die Felder der Dataclass gezeigt, nicht ihr Name.

Bemerkung

Den Dekorator @~dataclasses.dataclass zu Enum und seinen Unterklassen hinzuzufügen wird nicht unterstützt. Es löst keine Fehler aus, führt zur Laufzeit aber zu sehr seltsamen Ergebnissen, etwa dazu, dass Mitglieder einander gleich sind:

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

Pickling

Aufzählungen lassen sich mit pickle serialisieren und wieder einlesen:

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

Die üblichen Einschränkungen für pickle gelten: Enums, die sich serialisieren lassen, müssen auf der obersten Ebene eines Moduls definiert sein, weil sie zum Einlesen aus diesem Modul importierbar sein müssen.

Bemerkung

Mit pickle-Protokollversion 4 lassen sich Enums, die in anderen Klassen stecken, mühelos serialisieren.

Es ist möglich, zu verändern, wie Enum-Mitglieder serialisiert und eingelesen werden – indem man __reduce_ex__() in der Aufzählungsklasse definiert. Standardmäßig geschieht das über den Wert, aber Enums mit komplizierten Werten wollen möglicherweise über den Namen gehen:

>>> import enum
>>> class MyEnum(enum.Enum):
...     __reduce_ex__ = enum.pickle_by_enum_name

Bemerkung

Bei Flags ist der Weg über den Namen nicht empfehlenswert, weil unbenannte Aliase nicht wieder eingelesen werden.

Functional API

Die Klasse Enum ist aufrufbar und bietet die folgende funktionale API:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

Die Semantik dieser API ähnelt namedtuple. Das erste Argument des Aufrufs von Enum ist der Name der Aufzählung.

Das zweite Argument ist die Quelle der Namen der Aufzählungsmitglieder. Sie kann eine durch Leerraum getrennte Zeichenkette von Namen sein, eine Folge von Namen, eine Folge von 2-Tupeln mit Schlüssel-Wert-Paaren oder eine Abbildung (etwa ein Wörterbuch) von Namen auf Werte. Die letzten zwei Möglichkeiten erlauben es, Aufzählungen beliebige Werte zuzuweisen. Die anderen weisen automatisch aufsteigende Ganzzahlen ab 1 zu (verwende den Parameter start, um einen anderen Startwert anzugeben). Zurückgegeben wird eine neue, von Enum abgeleitete Klasse. Anders gesagt entspricht die obige Zuweisung an Animal Folgendem:

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

Der Grund, standardmäßig bei 1 und nicht bei 0 anzufangen, ist, dass 0 im booleschen Sinne False ist, Enum-Mitglieder standardmäßig aber alle zu True auswerten.

Enums, die mit der funktionalen API erzeugt wurden, zu serialisieren kann heikel sein, weil Implementierungsdetails des Frame-Stapels verwendet werden, um herauszufinden, in welchem Modul die Aufzählung erzeugt wird (es scheitert zum Beispiel, wenn du eine Hilfsfunktion in einem separaten Modul verwendest, und funktioniert möglicherweise auch nicht unter IronPython oder Jython). Die Lösung ist, den Modulnamen ausdrücklich anzugeben:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

Warnung

Wird module nicht angegeben und kann Enum es nicht bestimmen, lassen sich die neuen Enum-Mitglieder nicht wieder einlesen, damit Fehler näher an ihrer Quelle bleiben, wird das Serialisieren abgeschaltet.

Das neue pickle-Protokoll 4 stützt sich unter bestimmten Umständen außerdem darauf, dass __qualname__ auf den Ort gesetzt ist, an dem pickle die Klasse finden kann. Wurde die Klasse zum Beispiel in der Klasse SomeData im globalen Gültigkeitsbereich verfügbar gemacht:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

Die vollständige Signatur ist:

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • value: Was die neue Enum-Klasse als ihren Namen festhält.

  • names: Die Enum-Mitglieder. Das kann eine durch Leerraum oder Kommas getrennte Zeichenkette sein (die Werte beginnen bei 1, sofern nichts anderes angegeben ist):

    'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
    

    oder ein Iterator von Namen:

    ['RED', 'GREEN', 'BLUE']
    

    oder ein Iterator von (Name, Wert)-Paaren:

    [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
    

    oder eine Abbildung:

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module: Name des Moduls, in dem die neue Enum-Klasse zu finden ist.

  • qualname: wo im Modul die neue Enum-Klasse zu finden ist.

  • type: Typ, der in die neue Enum-Klasse eingemischt wird.

  • start: Zahl, bei der zu zählen begonnen wird, wenn nur Namen übergeben werden.

Geändert in Version 3.5: Der Parameter start wurde ergänzt.

Abgeleitete Aufzählungen

IntEnum

Die erste angebotene Abwandlung von Enum ist zugleich eine Unterklasse von int. Mitglieder eines IntEnum lassen sich mit Ganzzahlen vergleichen. In der Folge lassen sich ganzzahlige Aufzählungen unterschiedlicher Typen auch miteinander vergleichen:

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

Mit gewöhnlichen Enum-Aufzählungen lassen sie sich aber weiterhin nicht vergleichen:

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

IntEnum-Werte verhalten sich auch auf andere Weisen wie Ganzzahlen, wie du es erwartest:

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

StrEnum

Die zweite angebotene Abwandlung von Enum ist zugleich eine Unterklasse von str. Mitglieder eines StrEnum lassen sich mit Zeichenketten vergleichen. In der Folge lassen sich Zeichenketten-Aufzählungen unterschiedlicher Typen auch miteinander vergleichen.

Added in version 3.11.

IntFlag

Die nächste angebotene Abwandlung von Enum, IntFlag, beruht ebenfalls auf int. Der Unterschied ist, dass sich Mitglieder von IntFlag mit den bitweisen Operatoren (&, |, ^, ~) verbinden lassen und das Ergebnis, wenn möglich, weiterhin ein IntFlag-Mitglied ist. Wie bei IntEnum sind IntFlag-Mitglieder auch Ganzzahlen und lassen sich überall verwenden, wo ein int verwendet wird.

Bemerkung

Jede Operation auf einem IntFlag-Mitglied außer den bitweisen Operationen verliert die IntFlag-Zugehörigkeit.

Bitweise Operationen, die zu ungültigen IntFlag-Werten führen, verlieren die IntFlag-Zugehörigkeit. Näheres steht bei FlagBoundary.

Added in version 3.6.

Geändert in Version 3.11.

Beispiel einer IntFlag-Klasse:

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

Es ist auch möglich, die Kombinationen zu benennen:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

Bemerkung

Benannte Kombinationen gelten als Aliase. Aliase erscheinen beim Iterieren nicht, können aber beim Nachschlagen über den Wert zurückgegeben werden.

Geändert in Version 3.11.

Ein weiterer wichtiger Unterschied zwischen IntFlag und Enum ist: Ist kein Flag gesetzt (der Wert ist 0), ist die boolesche Auswertung False:

>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False

Weil IntFlag-Mitglieder auch Unterklassen von int sind, lassen sie sich mit diesen verbinden (verlieren dabei aber möglicherweise die IntFlag-Zugehörigkeit:

>>> Perm.X | 4
<Perm.R|X: 5>

>>> Perm.X + 8
9

Bemerkung

Der Negationsoperator ~ gibt immer ein IntFlag-Mitglied mit positivem Wert zurück:

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

Über IntFlag-Mitglieder lässt sich außerdem iterieren:

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

Added in version 3.11.

Flag

Die letzte Abwandlung ist Flag. Wie bei IntFlag lassen sich Flag-Mitglieder mit den bitweisen Operatoren (&, |, ^, ~) verbinden. Anders als bei IntFlag lassen sie sich weder mit einer anderen Flag-Aufzählung noch mit int verbinden oder vergleichen. Die Werte lassen sich zwar unmittelbar angeben, es ist aber empfehlenswert, auto als Wert zu verwenden und Flag einen passenden Wert wählen zu lassen.

Added in version 3.6.

Wie bei IntFlag ist die boolesche Auswertung False, wenn eine Kombination von Flag-Mitgliedern dazu führt, dass kein Flag gesetzt ist:

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False

Einzelne Flags sollten Werte haben, die Potenzen von zwei sind (1, 2, 4, 8, …), Kombinationen von Flags dagegen nicht:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Dem Zustand „kein Flag gesetzt“ einen Namen zu geben ändert seinen booleschen Wert nicht:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Über Flag-Mitglieder lässt sich außerdem iterieren:

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

Added in version 3.11.

Bemerkung

Für den Großteil neuen Codes sind Enum und Flag sehr empfehlenswert, weil IntEnum und IntFlag einige semantische Versprechen einer Aufzählung brechen (indem sie mit Ganzzahlen und damit über die Transitivität mit anderen, nicht verwandten Aufzählungen vergleichbar sind). IntEnum und IntFlag sollten nur in Fällen verwendet werden, in denen Enum und Flag nicht genügen – zum Beispiel, wenn ganzzahlige Konstanten durch Aufzählungen ersetzt werden, oder für das Zusammenspiel mit anderen Systemen.

Weitere

Zwar ist IntEnum Teil des Moduls enum, es wäre aber sehr einfach, es selbst umzusetzen:

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

Das zeigt, wie sich ähnliche abgeleitete Aufzählungen definieren lassen – zum Beispiel ein FloatEnum, das float anstelle von int einmischt.

Einige Regeln:

  1. Beim Ableiten von Enum müssen Mixin-Typen in der Folge der Basisklassen vor der Enum-Klasse selbst stehen, wie im IntEnum-Beispiel oben.

  2. Mixin-Typen müssen ableitbar sein. So sind zum Beispiel bool und range nicht ableitbar und lösen beim Erzeugen des Enums einen Fehler aus, wenn sie als Mixin-Typ verwendet werden.

  3. Ein Enum kann zwar Mitglieder jedes Typs haben, aber sobald du einen zusätzlichen Typ einmischst, müssen alle Mitglieder Werte dieses Typs haben, etwa int oben. Diese Einschränkung gilt nicht für Mixins, die nur Methoden ergänzen und keinen weiteren Typ angeben.

  4. Wird ein anderer Datentyp eingemischt, ist das Attribut value nicht dasselbe wie das Enum-Mitglied selbst, auch wenn es gleichwertig ist und sich als gleich vergleicht.

  5. Ein data type ist ein Mixin, das __new__() definiert, oder eine dataclass

  6. Formatierung im %-Stil: %s und %r rufen __str__() beziehungsweise __repr__() der Enum-Klasse auf. Andere Codes (etwa %i oder %h bei IntEnum) behandeln das Enum-Mitglied als seinen eingemischten Typ.

  7. Formatierte Zeichenkettenliterale, str.format() und format() verwenden die Methode __str__() des Enums.

Bemerkung

Weil IntEnum, IntFlag und StrEnum als austauschbarer Ersatz für bestehende Konstanten entworfen sind, wurde ihre Methode __str__() auf die Methode __str__() ihres Datentyps zurückgesetzt.

Wann __new__() und wann __init__()

__new__() muss immer dann verwendet werden, wenn du den tatsächlichen Wert des Enum-Mitglieds anpassen willst. Alle anderen Änderungen können entweder in __new__() oder in __init__() stehen, wobei __init__() zu bevorzugen ist.

Wenn du zum Beispiel mehrere Dinge an den Konstruktor übergeben willst, aber nur eines davon der Wert sein soll:

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Warnung

Rufe super().__new__() nicht auf, weil das nur zum Nachschlagen gedachte __new__ gefunden wird. Verwende stattdessen den Datentyp unmittelbar.

Feinere Punkte

Unterstützte __dunder__- und _sunder_-Namen

Die unterstützten __dunder__- und _sunder_-Namen stehen in der API-Dokumentation zu Enum.

_Private__names

Private Namen werden nicht in Enum-Mitglieder umgewandelt, sondern bleiben gewöhnliche Attribute.

Geändert in Version 3.11.

Der Typ von Enum-Mitgliedern

Enum-Mitglieder sind Instanzen ihrer Enum-Klasse und werden normalerweise als EnumClass.member angesprochen. In bestimmten Lagen, etwa beim Schreiben eigenen Enum-Verhaltens, ist es nützlich, von einem Mitglied unmittelbar auf ein anderes zugreifen zu können, und das wird unterstützt. Um Namenskollisionen zwischen Mitgliedsnamen und Attributen beziehungsweise Methoden eingemischter Klassen zu vermeiden, sind Großbuchstaben-Namen aber sehr empfehlenswert.

Geändert in Version 3.5.

Mitglieder erzeugen, die mit anderen Datentypen gemischt sind

Wenn du mit einem Enum von anderen Datentypen ableitest, etwa von int oder str, werden alle Werte nach dem = an den Konstruktor dieses Datentyps übergeben. Zum Beispiel:

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

Boolescher Wert von Enum-Klassen und -Mitgliedern

Enum-Klassen, die mit Typen gemischt sind, die keine Enum sind (etwa int, str usw.), werden nach den Regeln des eingemischten Typs ausgewertet. Andernfalls werten alle Mitglieder zu True aus. Damit die boolesche Auswertung deines eigenen Enums vom Wert des Mitglieds abhängt, ergänze in deiner Klasse Folgendes:

def __bool__(self):
    return bool(self.value)

Einfache Enum-Klassen werten immer zu True aus.

Enum-Klassen mit Methoden

Wenn du deiner Enum-Unterklasse zusätzliche Methoden gibst, wie der Klasse Planet unten, erscheinen diese Methoden in einem dir() des Mitglieds, aber nicht in dem der Klasse:

>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']

Mitglieder von Flag verbinden

Beim Iterieren über eine Kombination von Flag-Mitgliedern kommen nur die Mitglieder zurück, die aus einem einzigen Bit bestehen:

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

Kleinigkeiten zu Flag und IntFlag

Wir verwenden für unsere Beispiele den folgenden Ausschnitt:

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

Dann gilt Folgendes:

  • Flags mit einem einzelnen Bit sind kanonisch

  • Flags mit mehreren Bits und mit null Bits sind Aliase

  • beim Iterieren werden nur kanonische Flags zurückgegeben:

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • ein Flag oder eine Flag-Menge zu negieren gibt ein neues Flag beziehungsweise eine neue Flag-Menge mit dem entsprechenden positiven Ganzzahlwert zurück:

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • Namen von Pseudo-Flags werden aus den Namen ihrer Mitglieder gebildet:

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • Flags mit mehreren Bits, also Aliase, können aus Operationen zurückkommen:

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • Prüfung auf Zugehörigkeit beziehungsweise Enthaltensein: Flags mit dem Wert null gelten immer als enthalten:

    >>> Color.BLACK in Color.WHITE
    True
    

    andernfalls wird True nur dann zurückgegeben, wenn alle Bits des einen Flags im anderen Flag enthalten sind:

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

Es gibt einen neuen Grenzmechanismus, der steuert, wie Bits außerhalb des Bereichs beziehungsweise ungültige Bits behandelt werden: STRICT, CONFORM, EJECT und KEEP:

  • STRICT –> löst bei ungültigen Werten eine Ausnahme aus

  • CONFORM –> verwirft alle ungültigen Bits

  • EJECT –> verliert den Flag-Status und wird zu einem gewöhnlichen int mit dem gegebenen Wert

  • KEEP –> behält die zusätzlichen Bits

    • behält den Flag-Status und die zusätzlichen Bits

    • zusätzliche Bits erscheinen beim Iterieren nicht

    • zusätzliche Bits erscheinen in repr() und str()

Die Vorgabe für Flag ist STRICT, die Vorgabe für IntFlag ist EJECT, und die Vorgabe für _convert_ ist KEEP (ein Beispiel dafür, wann KEEP gebraucht wird, ist ssl.Options).

Wie unterscheiden sich Enums und Flags?

Enums haben eine eigene Metaklasse, die viele Aspekte sowohl der abgeleiteten Enum-Klassen als auch ihrer Instanzen (Mitglieder) beeinflusst.

Enum-Klassen

Die Metaklasse EnumType ist dafür zuständig, __contains__(), __dir__(), __iter__() und weitere Methoden bereitzustellen, mit denen man mit einer Enum-Klasse Dinge tun kann, die bei einer gewöhnlichen Klasse scheitern, etwa list(Color) oder some_enum_var in Color. EnumType ist dafür zuständig, sicherzustellen, dass verschiedene andere Methoden der endgültigen Enum-Klasse korrekt sind (etwa __new__(), __getnewargs__(), __str__() und __repr__()).

Flag-Klassen

Flags haben eine erweiterte Sicht auf Aliase: Um kanonisch zu sein, muss der Wert eines Flags eine Potenz von zwei sein und darf kein doppelter Name sein. Zusätzlich zur Enum-Begriffsbestimmung eines Alias gilt daher ein Flag ohne Wert (also 0) oder mit mehr als einem Wert, der eine Potenz von zwei ist (etwa 3), als Alias.

Enum-Mitglieder (also Instanzen)

Das interessanteste an Enum-Mitgliedern ist, dass sie Singletons sind. EnumType erzeugt sie alle, während es die Enum-Klasse selbst erzeugt, und setzt dann ein eigenes __new__() ein, das sicherstellt, dass nie neue erzeugt werden – indem es nur die bestehenden Mitgliedsinstanzen zurückgibt.

Flag-Mitglieder

Über Flag-Mitglieder lässt sich genauso iterieren wie über die Flag-Klasse, und es kommen nur die kanonischen Mitglieder zurück. Zum Beispiel:

>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]

(Beachte, dass BLACK, PURPLE und WHITE nicht erscheinen.)

Ein Flag-Mitglied zu invertieren gibt den entsprechenden positiven Wert zurück und nicht einen negativen Wert – zum Beispiel:

>>> ~Color.RED
<Color.GREEN|BLUE: 6>

Flag-Mitglieder haben eine Länge, die der Anzahl der Werte entspricht, die sie enthalten und die Potenzen von zwei sind. Zum Beispiel:

>>> len(Color.PURPLE)
2

Enum-Kochbuch

Enum, IntEnum, StrEnum, Flag und IntFlag dürften den Großteil der Anwendungsfälle abdecken, aber nicht alle. Hier Rezepte für einige verschiedene Arten von Aufzählungen, die sich unmittelbar verwenden lassen oder als Beispiele dienen, um eigene zu bauen.

Werte weglassen

In vielen Anwendungsfällen ist es einem gleichgültig, welchen Wert eine Aufzählung tatsächlich hat. Es gibt mehrere Wege, diese Art einfacher Aufzählung zu definieren:

  • Instanzen von auto als Wert verwenden

  • Instanzen von object als Wert verwenden

  • eine beschreibende Zeichenkette als Wert verwenden

  • ein Tupel als Wert und ein eigenes __new__() verwenden, um das Tupel durch einen int-Wert zu ersetzen

Jeden dieser Wege zu verwenden zeigt dem Benutzer, dass diese Werte nicht wichtig sind, und erlaubt es außerdem, Mitglieder hinzuzufügen, zu entfernen oder umzuordnen, ohne die übrigen Mitglieder neu zu nummerieren.

auto verwenden

auto zu verwenden sähe so aus:

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

object verwenden

object zu verwenden sähe so aus:

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>

Das ist auch ein gutes Beispiel dafür, warum du vielleicht dein eigenes __repr__() schreiben willst:

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

Eine beschreibende Zeichenkette verwenden

Eine Zeichenkette als Wert zu verwenden sähe so aus:

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

Ein eigenes __new__() verwenden

Ein automatisch nummerierendes __new__() zu verwenden sähe so aus:

>>> class AutoNumber(Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>

Um ein allgemeiner verwendbares AutoNumber zu bauen, ergänze *args in der Signatur:

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

Wenn du dann von AutoNumber erbst, kannst du dein eigenes __init__ schreiben, um zusätzliche Argumente zu behandeln:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Bemerkung

Die Methode __new__() wird, falls definiert, beim Erzeugen der Enum-Mitglieder verwendet. Sie wird dann durch das __new__() von Enum ersetzt, das nach der Klassenerzeugung zum Nachschlagen bestehender Mitglieder dient.

Warnung

Rufe super().__new__() nicht auf, weil das nur zum Nachschlagen gedachte __new__ gefunden wird. Verwende stattdessen den Datentyp unmittelbar – etwa so:

obj = int.__new__(cls, value)

OrderedEnum

Eine geordnete Aufzählung, die nicht auf IntEnum beruht und daher die üblichen Enum-Invarianten erhält (etwa, nicht mit anderen Aufzählungen vergleichbar zu sein):

>>> class OrderedEnum(Enum):
...     def __ge__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value >= other.value
...         return NotImplemented
...     def __gt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value > other.value
...         return NotImplemented
...     def __le__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value <= other.value
...         return NotImplemented
...     def __lt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value < other.value
...         return NotImplemented
...
>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

DuplicateFreeEnum

Löst einen Fehler aus, wenn ein doppelter Mitgliedswert gefunden wird, anstatt einen Alias zu erzeugen:

>>> class DuplicateFreeEnum(Enum):
...     def __init__(self, *args):
...         cls = self.__class__
...         if any(self.value == e.value for e in cls):
...             a = self.name
...             e = cls(self.value).name
...             raise ValueError(
...                 "aliases not allowed in DuplicateFreeEnum:  %r --> %r"
...                 % (a, e))
...
>>> class Color(DuplicateFreeEnum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...     GRENE = 2
...
Traceback (most recent call last):
  ...
ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'

Bemerkung

Das ist ein nützliches Beispiel dafür, von Enum abzuleiten, um anderes Verhalten zu ergänzen oder zu ändern und zugleich Aliase zu verbieten. Soll nur das Verbieten von Aliasen geändert werden, lässt sich stattdessen der Dekorator unique() verwenden.

MultiValueEnum

Unterstützt mehr als einen Wert je Mitglied:

>>> class MultiValueEnum(Enum):
...     def __new__(cls, value, *values):
...         self = object.__new__(cls)
...         self._value_ = value
...         for v in values:
...             self._add_value_alias_(v)
...         return self
...
>>> class DType(MultiValueEnum):
...     float32 = 'f', 8
...     double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>

Planet

Ist __new__() oder __init__() definiert, wird der Wert des Enum-Mitglieds an diese Methoden übergeben:

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

TimePeriod

Ein Beispiel, das das Attribut _ignore_ im Einsatz zeigt:

>>> import datetime as dt
>>> class Period(dt.timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

Von EnumType ableiten

Die meisten Bedürfnisse rund um Enums lassen sich dadurch erfüllen, dass man Enum-Unterklassen anpasst – entweder mit Klassendekoratoren oder mit eigenen Funktionen. Es ist aber möglich, von EnumType abzuleiten, um ein anderes Enum-Erlebnis zu bieten.