Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 435 – Hinzufügen eines Enum-Typs zur Python-Standardbibliothek

Autor:
Barry Warsaw <barry at python.org>, Eli Bendersky <eliben at gmail.com>, Ethan Furman <ethan at stoneleaf.us>
Status:
Final
Typ:
Standards Track
Erstellt:
23-Feb-2013
Python-Version:
3.4
Post-History:
23-Feb-2013, 02-May-2013
Ersetzt:
354
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP schlägt die Aufnahme eines Aufzählungstyps in die Python-Standardbibliothek vor.

Eine Aufzählung ist eine Menge von symbolischen Namen, die eindeutigen, konstanten Werten zugeordnet sind. Innerhalb einer Aufzählung können die Werte nach Identität verglichen werden, und die Aufzählung selbst kann durchlaufen werden.

Status der Diskussionen

Die Idee, einen Enum-Typ zu Python hinzuzufügen, ist nicht neu – PEP 354 ist ein früherer Versuch, der 2005 abgelehnt wurde. Kürzlich wurde eine neue Diskussionsrunde [3] auf der Mailingliste python-ideas initiiert. Viele neue Ideen wurden in mehreren Threads vorgeschlagen; nach einer langen Diskussion schlug Guido die Aufnahme von flufl.enum in die Standardbibliothek vor [4]. Während des PyCon 2013 Language Summit wurde das Thema weiter diskutiert. Es wurde deutlich, dass viele Entwickler ein Enum sehen möchten, das von int abgeleitet ist, was es uns ermöglichen würde, viele Integer-Konstanten in der Standardbibliothek durch Enums mit benutzerfreundlichen String-Darstellungen zu ersetzen, ohne die Abwärtskompatibilität zu gefährden. Eine weitere Diskussion unter mehreren interessierten Kernentwicklern führte zum Vorschlag, IntEnum als Sonderfall von Enum zu haben.

Das zentrale Trennungsthema zwischen Enum und IntEnum ist, ob der Vergleich mit Integern semantisch sinnvoll ist. Für die meisten Anwendungen von Aufzählungen ist es ein Merkmal, den Vergleich mit Integern abzulehnen; Enums, die mit Integern verglichen werden, führen durch Transitivität zu Vergleichen zwischen Enums nicht zusammenhängender Typen, was in den meisten Fällen unerwünscht ist. Für einige Anwendungen ist jedoch eine größere Interoperabilität mit Integern erwünscht. Dies ist beispielsweise der Fall, wenn bestehende Konstanten der Standardbibliothek (wie socket.AF_INET) durch Aufzählungen ersetzt werden.

Weitere Diskussionen Ende April 2013 führten zu dem Schluss, dass Enum-Elemente zum Typ ihres Enums gehören sollten: type(Color.red) == Color. Guido hat eine Entscheidung zu diesem Thema getroffen [5], ebenso wie zu verwandten Fragen, die Unterklassenbildung von Enums nicht zu erlauben [6], es sei denn, sie definieren keine Enum-Elemente [7].

Das PEP wurde von Guido am 10. Mai 2013 angenommen [1].

Motivation

[Teilweise basierend auf der Motivation in PEP 354]

Die Eigenschaften einer Aufzählung sind nützlich für die Definition einer unveränderlichen, zusammenhängenden Menge von konstanten Werten, die eine semantische Bedeutung haben können oder nicht. Klassische Beispiele sind Wochentage (Sonntag bis Samstag) und Schulnoten („A“ bis „D“ und „F“). Weitere Beispiele sind Fehlstatuswerte und Zustände innerhalb eines definierten Prozesses.

Es ist möglich, einfach eine Sequenz von Werten eines anderen Basistyps, wie int oder str, zu definieren, um diskrete beliebige Werte darzustellen. Eine Aufzählung stellt jedoch sicher, dass solche Werte von allen anderen, einschließlich, was wichtig ist, von Werten innerhalb anderer Aufzählungen, getrennt sind und dass Operationen ohne Bedeutung („Mittwoch mal zwei“) für diese Werte nicht definiert sind. Sie bietet auch eine bequeme druckbare Darstellung von Enum-Werten, ohne dass mühsame Wiederholungen bei der Definition erforderlich sind (d.h. keine GREEN = 'green').

Modul- und Typname

Wir schlagen vor, ein Modul namens enum zur Standardbibliothek hinzuzufügen. Der Haupttyp, der von diesem Modul bereitgestellt wird, ist Enum. Um den Enum-Typ zu importieren, wird der Benutzer-Code ausführen

>>> from enum import Enum

Vorgeschlagene Semantik für den neuen Aufzählungstyp

Erstellen eines Enums

Aufzählungen werden mit der Klassensyntax erstellt, was sie einfach zu lesen und zu schreiben macht. Eine alternative Erstellungsmethode wird unter Funktionale API beschrieben. Um eine Aufzählung zu definieren, leiten Sie von Enum wie folgt ab

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

Anmerkung zur Nomenklatur: Wir nennen Color eine Aufzählung (oder Enum) und Color.red, Color.green sind Aufzählungselemente (oder Enum-Elemente). Aufzählungselemente haben auch Werte (der Wert von Color.red ist 1 usw.)

Aufzählungselemente haben lesbare String-Darstellungen

>>> print(Color.red)
Color.red

…während ihr repr mehr Informationen enthält

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

Der Typ eines Aufzählungselements ist die Aufzählung, zu der es gehört

>>> type(Color.red)
<Enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>

Enums haben auch eine Eigenschaft, die nur ihren Elementnamen enthält

>>> print(Color.red.name)
red

Aufzählungen unterstützen die Iteration, in Definitionsreihenfolge

>>> class Shake(Enum):
...   vanilla = 7
...   chocolate = 4
...   cookies = 9
...   mint = 3
...
>>> for shake in Shake:
...   print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint

Aufzählungselemente sind hashbar, sodass sie in Dictionaries und Sets verwendet werden können

>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples
{<Color.red: 1>: 'red delicious', <Color.green: 2>: 'granny smith'}

Programmatischer Zugriff auf Enum-Elemente

Manchmal ist es nützlich, auf Enum-Elemente programmatisch zuzugreifen (d.h. in Situationen, in denen Color.red nicht ausreicht, da die genaue Farbe zur Zeit der Programmierung nicht bekannt ist). Enum erlaubt einen solchen Zugriff

>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>

Wenn Sie über den Namen auf Enum-Elemente zugreifen möchten, verwenden Sie den Elementzugriff

>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>

Duplizieren von Enum-Elementen und Werten

Zwei Enum-Elemente mit demselben Namen sind ungültig

>>> class Shape(Enum):
...   square = 2
...   square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: square

Zwei Enum-Elemente dürfen jedoch denselben Wert haben. Bei zwei Elementen A und B mit demselben Wert (und A ist zuerst definiert), ist B ein Alias für A. Die Suche nach dem Wert von A und B nach Wert gibt A zurück. Die Suche nach B nach Name gibt ebenfalls 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>

Die Iteration über die Elemente eines Enums liefert nicht die Aliase

>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]

Das spezielle Attribut __members__ ist ein geordnetes Dictionary, das Namen auf Elemente abbildet. Es enthält alle in der Aufzählung definierten Namen, einschließlich der 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__ kann für detaillierten programmatischen Zugriff auf die Enum-Elemente verwendet werden. Zum Beispiel, um alle Aliase zu finden

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

Vergleiche

Aufzählungselemente werden nach Identität verglichen

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

Geordnete Vergleiche zwischen Aufzählungswerten werden nicht unterstützt. Enums sind keine Integer (aber siehe IntEnum unten)

>>> Color.red < Color.blue
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Color() < Color()

Gleichheitsvergleiche sind jedoch definiert

>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True

Vergleiche mit Nicht-Aufzählungswerten werden immer als ungleich verglichen (auch hier verhält sich IntEnum bewusst anders, siehe unten)

>>> Color.blue == 2
False

Zugelassene Elemente und Attribute von Aufzählungen

Die obigen Beispiele verwenden Integer für Aufzählungswerte. Die Verwendung von Integern ist kurz und praktisch (und wird standardmäßig von der Funktionalen API bereitgestellt), ist aber nicht zwingend vorgeschrieben. In den allermeisten Anwendungsfällen ist es egal, wie der tatsächliche Wert einer Aufzählung ist. Aber wenn der Wert wichtig ist, können Aufzählungen beliebige Werte haben.

Aufzählungen sind Python-Klassen und können wie gewohnt Methoden und spezielle 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, was erlaubt ist, lauten wie folgt: Alle Attribute, die innerhalb einer Aufzählung definiert sind, werden zu Elementen dieser Aufzählung, mit Ausnahme von __dunder__-Namen und Deskriptoren [9]; Methoden sind ebenfalls Deskriptoren.

Eingeschränkte Unterklassebildung von Aufzählungen

Die Unterklassebildung einer Aufzählung ist nur erlaubt, wenn die Aufzählung keine Elemente definiert. Dies ist also verboten

>>> class MoreColor(Color):
...   pink = 17
...
TypeError: Cannot extend enumerations

Aber das ist erlaubt

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

Die Begründung für diese Entscheidung wurde von Guido in [6] gegeben. Die Erlaubnis zur Unterklassebildung von Enums, die Elemente definieren, würde zu einer Verletzung wichtiger Invarianten von Typen und Instanzen führen. Andererseits ist es sinnvoll, gemeinsames Verhalten zwischen einer Gruppe von Aufzählungen zu teilen, und die Unterklassebildung leerer Aufzählungen wird auch zur Implementierung von IntEnum verwendet.

IntEnum

Es wird eine Variante von Enum vorgeschlagen, die auch eine Unterklasse von int ist. Elemente eines IntEnum können mit Integern verglichen werden; im erweiterten Sinne können auch Integer-Aufzählungen verschiedener Typen miteinander verglichen werden

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

Sie können jedoch immer noch nicht mit Enum verglichen werden

>>> class Shape(IntEnum):
...   circle = 1
...   square = 2
...
>>> class Color(Enum):
...   red = 1
...   green = 2
...
>>> Shape.circle == Color.red
False

IntEnum-Werte verhalten sich auch in anderer Hinsicht wie Integer, wie Sie es erwarten würden

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

Für die überwiegende Mehrheit des Codes wird Enum dringend empfohlen, da IntEnum einige semantische Versprechen einer Aufzählung bricht (indem es mit Integern und damit transitiv mit anderen nicht verwandten Aufzählungen vergleichbar ist). Es sollte nur in Sonderfällen verwendet werden, in denen keine andere Wahl besteht; zum Beispiel, wenn Integer-Konstanten durch Aufzählungen ersetzt werden und Abwärtskompatibilität mit Code erforderlich ist, der immer noch Integer erwartet.

Andere abgeleitete Aufzählungen

IntEnum wird Teil des enum-Moduls sein. Es wäre jedoch sehr einfach, es unabhängig zu implementieren

class IntEnum(int, Enum):
    pass

Dies zeigt, wie ähnliche abgeleitete Aufzählungen definiert werden können, zum Beispiel ein StrEnum, das str anstelle von int einmixt.

Einige Regeln

  1. Beim Erstellen von Unterklassen von Enum müssen Mix-in-Typen vor Enum selbst in der Sequenz der Basisklassen stehen, wie im obigen IntEnum-Beispiel.
  2. Während Enum Elemente beliebigen Typs haben kann, müssen nach dem Einmischen eines zusätzlichen Typs alle Elemente Werte dieses Typs haben, z.B. int oben. Diese Einschränkung gilt nicht für Mix-ins, die nur Methoden hinzufügen und keinen anderen Datentyp wie int oder str spezifizieren.

Pickling

Aufzählungen können gepickelt und entpickelt werden

>>> from enum.tests.fruit import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True

Die üblichen Einschränkungen für das Pickling gelten: pickelbare Enums müssen auf der obersten Ebene eines Moduls definiert sein, da das Entpickeln verlangt, dass sie aus diesem Modul importierbar sind.

Funktionale 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>
>>> Animal.ant.value
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 Pickling von mit der funktionalen API erstellten Enums funktioniert auf CPython und PyPy, aber für IronPython und Jython müssen Sie möglicherweise den Modulnamen explizit angeben, wie folgt

>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)

Das zweite Argument ist die Quelle der Namen der Aufzählungselemente. Es kann ein durch Leerzeichen getrennter String von Namen, eine Sequenz von Namen, eine Sequenz von 2-Tupeln mit Schlüssel/Wert-Paaren oder eine Abbildung (z.B. ein Dictionary) von Namen zu Werten sein. Die letzten beiden Optionen ermöglichen die Zuweisung beliebiger Werte zu Aufzählungen; die anderen weisen automatisch aufsteigende Integer ab 1 zu. Eine neue Klasse, die von Enum abgeleitet ist, wird zurückgegeben. Mit anderen Worten, die obige Zuweisung an Animal ist äquivalent zu

>>> class Animals(Enum):
...   ant = 1
...   bee = 2
...   cat = 3
...   dog = 4

Der Grund für die Standardeinstellung auf 1 als Startzahl und nicht auf 0 ist, dass 0 im booleschen Sinne False ist, aber Enum-Elemente alle zu True ausgewertet werden.

Vorgeschlagene Variationen

Einige Variationen wurden während der Diskussionen auf der Mailingliste vorgeschlagen. Hier sind einige der beliebtesten.

flufl.enum

flufl.enum war die Referenzimplementierung, auf der dieses PEP ursprünglich basierte. Schließlich wurde beschlossen, das Paket flufl.enum nicht aufzunehmen, da sein Design Aufzählungselemente von Aufzählungen trennte, sodass erstere keine Instanzen letzterer sind. Sein Design erlaubt ausdrücklich die Unterklassebildung von Aufzählungen zur Erweiterung mit weiteren Elementen (aufgrund der Trennung von Elementen und Aufzählungen werden die Typinvarianten in flufl.enum bei einem solchen Schema nicht verletzt).

Keine Notwendigkeit, Werte für Enums anzugeben

Michael Foord schlug (und Tim Delaney lieferte eine Proof-of-Concept-Implementierung) die Verwendung von Metaklassen-Magie vor, die dies ermöglicht

class Color(Enum):
    red, green, blue

Die Werte werden tatsächlich erst zugewiesen, wenn sie zum ersten Mal abgerufen werden.

Vorteile: sauberere Syntax, die für eine sehr häufige Aufgabe weniger Tipparbeit erfordert (nur das Auflisten von Aufzählung-Namen ohne Berücksichtigung der Werte).

Nachteile: viel Magie in der Implementierung, die selbst die Definition solcher Enums beim ersten Anblick verwirrend macht. Außerdem ist explizit besser als implizit.

Verwendung spezieller Namen oder Formen zur automatischen Zuweisung von Enum-Werten

Ein anderer Ansatz, um Enum-Werte nicht angeben zu müssen, ist die Verwendung eines speziellen Namens oder einer speziellen Form zur automatischen Zuweisung. Zum Beispiel

class Color(Enum):
    red = None          # auto-assigned to 0
    green = None        # auto-assigned to 1
    blue = None         # auto-assigned to 2

Flexibler

class Color(Enum):
    red = 7
    green = None        # auto-assigned to 8
    blue = 19
    purple = None       # auto-assigned to 20

Einige Variationen dieses Themas

  1. Ein spezieller Name auto, importiert aus dem enum-Paket.
  2. Georg Brandl schlug Ellipsen (...) anstelle von None vor, um denselben Effekt zu erzielen.

Vorteile: keine Notwendigkeit, Werte manuell einzugeben. Erleichtert die Änderung und Erweiterung des Enums, insbesondere bei großen Aufzählungen.

Nachteile: in vielen einfachen Fällen tatsächlich länger zu tippen. Das Argument explizit vs. implizit gilt auch hier.

Anwendungsfälle in der Standardbibliothek

Die Python-Standardbibliothek hat viele Stellen, an denen die Verwendung von Enums vorteilhaft wäre, um andere Idiome zu ersetzen, die derzeit zu ihrer Darstellung verwendet werden. Solche Verwendungen können in zwei Kategorien unterteilt werden: für den Benutzer-Code sichtbare Konstanten und interne Konstanten.

Für den Benutzer-Code sichtbare Konstanten wie os.SEEK_*, Konstanten im socket-Modul, Dezimalrundungsmodi und HTML-Fehlercodes erfordern möglicherweise Abwärtskompatibilität, da der Benutzer-Code möglicherweise Integer erwartet. IntEnum, wie oben beschrieben, bietet die erforderliche Semantik; als Unterklasse von int beeinflusst es keinen Benutzer-Code, der Integer erwartet, während es andererseits druckbare Darstellungen für Aufzählungswerte ermöglicht

>>> import socket
>>> family = socket.AF_INET
>>> family == 2
True
>>> print(family)
SocketFamily.AF_INET

Interne Konstanten sind für den Benutzer-Code nicht sichtbar, werden aber intern von Stdlib-Modulen verwendet. Diese können mit Enum implementiert werden. Einige Beispiele, die bei einer sehr oberflächlichen Durchsicht der Stdlib aufgedeckt wurden: binhex, imaplib, http/client, urllib/robotparser, idlelib, concurrent.futures, turtledemo.

Darüber hinaus zeigen sich bei der Betrachtung des Codes der Twisted-Bibliothek viele Anwendungsfälle für den Ersatz interner Zustands-Konstanten durch Enums. Das gleiche gilt für viel Netzwerkcode (insbesondere die Implementierung von Protokollen) und ist auch in Testprotokollen zu sehen, die mit der Tulip-Bibliothek geschrieben wurden.

Danksagungen

Dieses PEP schlug ursprünglich die Aufnahme des Pakets flufl.enum [8] von Barry Warsaw in die Stdlib vor und ist in großen Teilen davon inspiriert. Ben Finney ist der Autor des früheren Aufzählungs-PEP PEP 354.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0435.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT