diff --git a/pep-0435.txt b/pep-0435.txt index 334ca2cbf..99148a6e1 100644 --- a/pep-0435.txt +++ b/pep-0435.txt @@ -3,22 +3,20 @@ Title: Adding an Enum type to the Python standard library Version: $Revision$ Last-Modified: $Date$ Author: Barry Warsaw , - Eli Bendersky + Eli Bendersky , + Ethan Furman Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 2013-02-23 Python-Version: 3.4 -Post-History: 2013-02-23 +Post-History: 2013-02-23, 2013-05-02 Abstract ======== This PEP proposes adding an enumeration type to the Python standard library. -Specifically, it proposes moving the existing ``flufl.enum`` package by Barry -Warsaw into the standard library. Much of this PEP is based on the "using" -[1]_ document from the documentation of ``flufl.enum``. An enumeration is a set of symbolic names bound to unique, constant values. Within an enumeration, the values can be compared by identity, and the @@ -28,7 +26,7 @@ enumeration itself can be iterated over. Decision ======== -TODO: update decision here once pronouncement is made. +TODO: update decision here once pronouncement is made. [1]_ Status of discussions @@ -55,9 +53,11 @@ interoperatiliby with integers is desired. For instance, this is the case for replacing existing standard library constants (such as ``socket.AF_INET``) with enumerations. -This PEP is an attempt to formalize this decision as well as discuss a number -of variations that were proposed and can be considered for inclusion. - +Further discussion in late April 2013 led to the conclusion that enumeration +members should belong to the type of their enum: ``type(Color.red) == Color``. +Guido has pronounced a decision on this issue [5]_, as well as related issues +of not allowing to subclass enums [6]_, unless they define no enumeration +members [7]_. Motivation ========== @@ -97,229 +97,151 @@ Creating an Enum Enumerations are created using the class syntax, which makes them easy to read and write. An alternative creation method is described in `Convenience API`_. -To define an enumeration, derive from the ``Enum`` class and add attributes -with assignment to their integer values:: +To define an enumeration, subclass ``Enum`` as follows:: >>> from enum import Enum - >>> class Colors(Enum): + >>> class Color(Enum): ... red = 1 ... green = 2 ... blue = 3 -Enumeration values have nice, human readable string representations:: +**A note on nomenclature**: we call ``Color`` an *enumeration* (or *enum*) +and ``Color.red``, ``Color.green`` are *enumeration members* (or +*enum members*). Enumeration members also have *values* (the value of +``Color.red`` is ``1``, etc.) - >>> print(Colors.red) - Colors.red +Enumeration members have human readable string representations:: -...while their repr has more information:: + >>> print(Color.red) + Color.red - >>> print(repr(Colors.red)) - +...while their ``repr`` has more information:: -You can get the enumeration class object from an enumeration value:: + >>> print(repr(Color.red)) + Color.red [value=1] - >>> cls = Colors.red.enum - >>> print(cls.__name__) - Colors +The *type* of an enumeration member is the enumeration it belongs to:: + + >>> type(Color.red) + + >>> isinstance(Color.green, Color) + True + >>> Enums also have a property that contains just their item name:: - >>> print(Colors.red.name) + >>> print(Color.red.name) red - >>> print(Colors.green.name) - green - >>> print(Colors.blue.name) - blue -The str and repr of the enumeration class also provides useful information:: +Enumerations support iteration, in definition order:: - >>> print(Colors) - - >>> print(repr(Colors)) - + >>> 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 -The ``Enum`` class supports iteration. Iteration order is undefined:: - - >>> from operator import attrgetter - >>> by_value = attrgetter('value') - >>> class FiveColors(Enum): - ... pink = 4 - ... cyan = 5 - ... green = 2 - ... blue = 3 - ... red = 1 - >>> [v.name for v in sorted(FiveColors, by_value)] - ['red', 'green', 'blue', 'pink', 'cyan'] - -Iteration order over `IntEnum`_ enumerations are guaranteed to be -sorted by value. - - >>> class Toppings(IntEnum): - ... anchovies = 4 - ... black_olives = 8 - ... cheese = 2 - ... dried_tomatoes = 16 - ... eggplant = 1 - - >>> for value in Toppings: - ... print(value.name, '=', value.value) - eggplant = 1 - cheese = 2 - anchovies = 4 - black_olives = 8 - dried_tomatoes = 16 - -Enumeration values are hashable, so they can be used in dictionaries and sets:: +Enumeration members are hashable, so they can be used in dictionaries and sets:: >>> apples = {} - >>> apples[Colors.red] = 'red delicious' - >>> apples[Colors.green] = 'granny smith' + >>> apples[Color.red] = 'red delicious' + >>> apples[Color.green] = 'granny smith' >>> apples - {: 'granny smith', : 'red delicious'} + {Color.red [value=1]: 'red delicious', Color.green [value=2]: 'granny smith'} -Programmatic access to enum values ----------------------------------- +Programmatic access to enumeration members +------------------------------------------ -Sometimes it's useful to access values in enumerations programmatically (i.e. -situations where ``Colors.red`` won't do because the exact color is not known -at program-writing time). ``Enum`` allows such access by value:: +Sometimes it's useful to access members in enumerations programmatically (i.e. +situations where ``Color.red`` won't do because the exact color is not known +at program-writing time). ``Enum`` allows such access:: - >>> Colors[1] - - >>> Colors[2] - + >>> Color(1) + Color.red [value=1] + >>> Color(3) + Color.blue [value=3] -For consistency, an ``EnumValue`` can be used in the same access pattern:: - - >>> Colors[Colors.red] - - >>> Colors[Colors.green] - - -If you want to access enum values by *name*, ``Enum`` works as expected with +If you want to access enum members by *name*, ``Enum`` works as expected with ``getattr``:: - >>> getattr(Colors, 'red') - - >>> getattr(Colors, 'green') - + >>> getattr(Color, 'red') + Color.red [value=1] + >>> getattr(Color, 'green') + Color.green [value=2] + +Duplicating enum members and values +----------------------------------- + +Having two enum members with the same name is invalid:: + + >>> class Shape(Enum): + ... square = 2 + ... square = 3 + ... + Traceback (most recent call last): + ... + TypeError: Attempted to reuse key: square + +However, two enum members are allowed to have the same value. By-value lookup +will then access the *earliest defined* member:: + + >>> class Shape(Enum): + ... square = 2 + ... diamond = 1 + ... circle = 3 + ... alias_for_square = 2 + ... + >>> Shape.square + Shape.square [value=2] + >>> Shape.alias_for_square + Shape.square [value=2] + >>> Shape(2) + Shape.square [value=2] Comparisons ----------- -Enumeration values are compared by identity:: +Enumeration members are compared by identity:: - >>> Colors.red is Colors.red + >>> Color.red is Color.red True - >>> Colors.blue is Colors.blue - True - >>> Colors.red is not Colors.blue - True - >>> Colors.blue is Colors.red + >>> Color.red is Color.blue False + >>> Color.red is not Color.blue + True Ordered comparisons between enumeration values are *not* supported. Enums are not integers (but see `IntEnum`_ below):: - >>> Colors.red < Colors.blue + >>> Color.red < Color.blue Traceback (most recent call last): - ... - NotImplementedError - >>> Colors.red <= Colors.blue - Traceback (most recent call last): - ... - NotImplementedError - >>> Colors.blue > Colors.green - Traceback (most recent call last): - ... - NotImplementedError - >>> Colors.blue >= Colors.green - Traceback (most recent call last): - ... - NotImplementedError + File "", line 1, in + TypeError: unorderable types: Color() < Color() Equality comparisons are defined though:: - >>> Colors.blue == Colors.blue - True - >>> Colors.green != Colors.blue + >>> Color.blue == Color.red + False + >>> Color.blue == Color.blue True -Comparisons against non-enumeration values will always compare not equal:: +Comparisons against non-enumeration values will always compare not equal +(again, ``IntEnum`` was explicitly designed to behave differently, see +below):: - >>> Colors.green == 2 - False - >>> Colors.blue == 3 - False - >>> Colors.green != 3 - True - >>> Colors.green == 'green' + >>> Color.blue == 2 False - -Extending enumerations by subclassing -------------------------------------- - -You can extend previously defined Enums by subclassing:: - - >>> class MoreColors(Colors): - ... pink = 4 - ... cyan = 5 - -When extended in this way, the base enumeration's values are identical to the -same named values in the derived class:: - - >>> Colors.red is MoreColors.red - True - >>> Colors.blue is MoreColors.blue - True - -However, these are not doing comparisons against the integer -equivalent values, because if you define an enumeration with similar -item names and integer values, they will not be identical:: - - >>> class OtherColors(Enum): - ... red = 1 - ... blue = 2 - ... yellow = 3 - >>> Colors.red is OtherColors.red - False - >>> Colors.blue is not OtherColors.blue - True - -Because ``Colors`` and ``OtherColors`` are unrelated enumerations, -their values are not equal, and thus they may exist in the same set, -or as distinct keys in the same dictionary:: - - >>> Colors.red == OtherColors.red - False - >>> len(set((Colors.red, OtherColors.red))) - 2 - -You may not define two enumeration values with the same integer value:: - - >>> class Bad(Enum): - ... cartman = 1 - ... stan = 2 - ... kyle = 3 - ... kenny = 3 # Oops! - ... butters = 4 - Traceback (most recent call last): - ... - ValueError: Conflicting enums with value '3': 'kenny' and 'kyle' - -You also may not duplicate values in derived enumerations:: - - >>> class BadColors(Colors): - ... yellow = 4 - ... chartreuse = 2 # Oops! - Traceback (most recent call last): - ... - ValueError: Conflicting enums with value '2': 'green' and 'chartreuse' - - -Enumeration values ------------------- +Allowed members and attributs of enumerations +--------------------------------------------- The examples above use integers for enumeration values. Using integers is short and handy (and provided by default by the `Convenience API`_), but not @@ -330,9 +252,9 @@ enumerations can have arbitrary values. The following example uses strings:: >>> class SpecialId(Enum): ... selector = '$IM($N)' ... adaptor = '~$IM' - ... + ... >>> SpecialId.selector - + SpecialId.selector [value='$IM($N)'] >>> SpecialId.selector.value '$IM($N)' >>> a = SpecialId.adaptor @@ -342,8 +264,6 @@ enumerations can have arbitrary values. The following example uses strings:: True >>> print(a) SpecialId.adaptor - >>> print(a.value) - ~$IM Here ``Enum`` is used to provide readable (and syntactically valid!) names for some special values, as well as group them together. @@ -353,14 +273,73 @@ very special cases. Code will be most readable when actual values of enumerations aren't important and enumerations are just used for their naming and comparison properties. +Enumerations are Python classes, and can have methods and special methods as +usual. If we have this enumeration:: + + 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 + +Then:: + + >>> Mood.favorite_mood() + Mood.happy [value=3] + >>> Mood.happy.describe() + ('happy', 3) + >>> str(Mood.funky) + 'my custom str! 1' + +The rules for what is allowed are as follows: all attributes defined within an +enumeration will become members of this enumeration, with the exception of +*__dunder__* names and descriptors; methods are descriptors too. + +Restricted subclassing of enumerations +-------------------------------------- + +Subclassing an enumeration is allowed only if the enumeration does not define +any members. So this is forbidden:: + + >>> class MoreColor(Color): + ... pink = 17 + ... + TypeError: Cannot subclass enumerations + +But this is allowed:: + + >>> class Foo(Enum): + ... def some_behavior(self): + ... pass + ... + >>> class Bar(Foo): + ... happy = 1 + ... sad = 2 + ... + +The rationale for this decision was given by Guido in [6]_. Allowing to +subclass enums that define members would lead to a violation of some +important invariants of types and instances. On the other hand, it +makes sense to allow sharing some common behavior between a group of +enumerations, and subclassing empty enumerations is also used to implement +``IntEnum``. IntEnum ------- -A variation of ``Enum`` is proposed where the enumeration values also -subclasses ``int`` - ``IntEnum``. These values can be compared to -integers; by extension, enumerations of different types can also be -compared to each other:: +A variation of ``Enum`` is proposed which is also a subclass of ``int``. +Members of an ``IntEnum`` can be compared to integers; by extension, +integer enumerations of different types can also be compared to each other:: >>> from enum import IntEnum >>> class Shape(IntEnum): @@ -384,11 +363,11 @@ However they still can't be compared to ``Enum``:: ... circle = 1 ... square = 2 ... - >>> class Colors(Enum): + >>> class Color(Enum): ... red = 1 ... green = 2 ... - >>> Shape.circle == Colors.red + >>> Shape.circle == Color.red False ``IntEnum`` values behave like integers in other ways you'd expect:: @@ -425,21 +404,23 @@ Convenience API The ``Enum`` class is callable, providing the following convenience API:: - >>> Animals = Enum('Animals', 'ant bee cat dog') - >>> Animals - - >>> Animals.ant - - >>> Animals.ant.value + >>> Animal = Enum('Animal', 'ant bee cat dog') + >>> Animal + + >>> Animal.ant + Animal.ant [value=1] + >>> Animal.ant.value 1 + >>> list(Animal) + [Animal.ant [value=1], Animal.bee [value=2], Animal.cat [value=3], Animal.dog [value=4]] The semantics of this API resemble ``namedtuple``. The first argument of the call to ``Enum`` is the name of the enumeration. The second argument is -a source of enumeration value names. It can be a whitespace-separated string +a source of enumeration member names. It can be a whitespace-separated string of names, a sequence of names or a sequence of 2-tuples with key/value pairs. The last option enables assigning arbitrary values to enumerations; the others auto-assign increasing integers starting with 1. A new class derived from -``Enum`` is returned. In other words, the above assignment to ``Animals`` is +``Enum`` is returned. In other words, the above assignment to ``Animal`` is equivalent to:: >>> class Animals(Enum): @@ -448,26 +429,22 @@ equivalent to:: ... cat = 3 ... dog = 4 -Examples of alternative name/value specifications:: - - >>> Enum('Animals', ['ant', 'bee', 'cat', 'dog']) - - >>> Enum('Animals', (('ant', 'one'), ('bee', 'two'), ('cat', 'three'), ('dog', 'four'))) - - -The second argument can also be a dictionary mapping names to values:: - - >>> levels = dict(debug=10, info=20, warning=30, severe=40) - >>> Enum('Levels', levels) - - - Proposed variations =================== Some variations were proposed during the discussions in the mailing list. Here's some of the more popular ones. +flufl.enum +---------- + +``flufl.enum`` was the reference implementation upon which this PEP was +originally based. Eventually, it was decided against the inclusion of +``flufl.enum`` because its design separated enumeration members from +enumerations, so the former are not instances of the latter. Its design +also explicitly permits subclassing enumerations for extending them with +more members (due to the member/enum separation, the type invariants are not +violated in ``flufl.enum`` with such a scheme). Not having to specify values for enums -------------------------------------- @@ -487,7 +464,6 @@ Cons: involves much magic in the implementation, which makes even the definition of such enums baffling when first seen. Besides, explicit is better than implicit. - Using special names or forms to auto-assign enum values ------------------------------------------------------- @@ -519,7 +495,6 @@ extend it, especially for large enumerations. Cons: actually longer to type in many simple cases. The argument of explicit vs. implicit applies here as well. - Use-cases in the standard library ================================= @@ -553,51 +528,24 @@ cases for replacing internal state constants with enums. The same can be said about a lot of networking code (especially implementation of protocols) and can be seen in test protocols written with the Tulip library as well. - -Differences from PEP 354 -======================== - -Unlike PEP 354, enumeration values are not defined as a sequence of strings, -but as attributes of a class. This design was chosen because it was felt that -class syntax is more readable. - -Unlike PEP 354, enumeration values require an explicit integer value. This -difference recognizes that enumerations often represent real-world values, or -must interoperate with external real-world systems. For example, to store an -enumeration in a database, it is better to convert it to an integer on the way -in and back to an enumeration on the way out. Providing an integer value also -provides an explicit ordering. However, there is no automatic conversion to -and from the integer values, because explicit is better than implicit. - -Unlike PEP 354, this implementation does use a metaclass to define the -enumeration's syntax, and allows for extended base-enumerations so that the -common values in derived classes are identical (a singleton model). While PEP -354 dismisses this approach for its complexity, in practice any perceived -complexity, though minimal, is hidden from users of the enumeration. - -Unlike PEP 354, enumeration values should only be tested by identity -comparison. This is to emphasize the fact that enumeration values are -singletons, much like ``None``. - - Acknowledgments =============== -This PEP describes the ``flufl.enum`` package by Barry Warsaw. ``flufl.enum`` -is based on an example by Jeremy Hylton. It has been modified and extended -by Barry Warsaw for use in the GNU Mailman [5]_ project. Ben Finney is the -author of the earlier enumeration PEP 354. - +This PEP was initially proposing including the ``flufl.enum`` package [8]_ +by Barry Warsaw into the stdlib, and is inspired in large parts by it. +Ben Finney is the author of the earlier enumeration PEP 354. References ========== -.. [1] http://pythonhosted.org/flufl.enum/docs/using.html +.. [1] Placeholder for pronouncement .. [2] http://www.python.org/dev/peps/pep-0354/ .. [3] http://mail.python.org/pipermail/python-ideas/2013-January/019003.html .. [4] http://mail.python.org/pipermail/python-ideas/2013-February/019373.html -.. [5] http://www.list.org - +.. [5] http://mail.python.org/pipermail/python-dev/2013-April/125687.html +.. [6] http://mail.python.org/pipermail/python-dev/2013-April/125716.html +.. [7] http://mail.python.org/pipermail/python-dev/2013-May/125859.html +.. [8] http://pythonhosted.org/flufl.enum/ Copyright ========= @@ -608,7 +556,8 @@ This document has been placed in the public domain. Todo ==== - * Mark PEP 354 "superseded by" this one, if accepted +* Mark PEP 354 "superseded by" this one, if accepted +* The last revision where flufl.enum was the approach is cb3c18a080a3 .. Local Variables: