Overhaul PEP 435 to describe the current state of decisions, which deviates

from flufl.enum:

1. type(Color.red) == Color
2. All attrs of an enum that are not dunders and are not descriptors become
   enumeration values
3. Given class Color(Enum), it's only valid to subclass Color if Color
   defines no enumeration values
4. As a corollary to (3), class IntEnum(int, Enum) is also part of the
   stdlib (and serves as an example for others)

Other issues that were contended recently but seem to have gotten resolved:

A. __call__ for by-value access, explicit getattr() for by-name access.
   __getitem__ is removed from Enum
B. __int__ is removed from Enum. It's present by definition in IntEnum

NOTE: this is still an early draft that is in the process of being reviewed,
so please treat it accordingly.
This commit is contained in:
Eli Bendersky 2013-05-02 05:51:33 -07:00
parent 801e07188f
commit d223705ffc
1 changed files with 204 additions and 255 deletions

View File

@ -3,22 +3,20 @@ Title: Adding an Enum type to the Python standard library
Version: $Revision$ Version: $Revision$
Last-Modified: $Date$ Last-Modified: $Date$
Author: Barry Warsaw <barry@python.org>, Author: Barry Warsaw <barry@python.org>,
Eli Bendersky <eliben@gmail.com> Eli Bendersky <eliben@gmail.com>,
Ethan Furman <ethan@stoneleaf.us>
Status: Draft Status: Draft
Type: Standards Track Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 2013-02-23 Created: 2013-02-23
Python-Version: 3.4 Python-Version: 3.4
Post-History: 2013-02-23 Post-History: 2013-02-23, 2013-05-02
Abstract Abstract
======== ========
This PEP proposes adding an enumeration type to the Python standard library. 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. 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 Within an enumeration, the values can be compared by identity, and the
@ -28,7 +26,7 @@ enumeration itself can be iterated over.
Decision Decision
======== ========
TODO: update decision here once pronouncement is made. TODO: update decision here once pronouncement is made. [1]_
Status of discussions 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``) replacing existing standard library constants (such as ``socket.AF_INET``)
with enumerations. with enumerations.
This PEP is an attempt to formalize this decision as well as discuss a number Further discussion in late April 2013 led to the conclusion that enumeration
of variations that were proposed and can be considered for inclusion. 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 Motivation
========== ==========
@ -97,229 +97,151 @@ Creating an Enum
Enumerations are created using the class syntax, which makes them easy to read Enumerations are created using the class syntax, which makes them easy to read
and write. An alternative creation method is described in `Convenience API`_. and write. An alternative creation method is described in `Convenience API`_.
To define an enumeration, derive from the ``Enum`` class and add attributes To define an enumeration, subclass ``Enum`` as follows::
with assignment to their integer values::
>>> from enum import Enum >>> from enum import Enum
>>> class Colors(Enum): >>> class Color(Enum):
... red = 1 ... red = 1
... green = 2 ... green = 2
... blue = 3 ... 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) Enumeration members have human readable string representations::
Colors.red
...while their repr has more information:: >>> print(Color.red)
Color.red
>>> print(repr(Colors.red)) ...while their ``repr`` has more information::
<EnumValue: Colors.red [value=1]>
You can get the enumeration class object from an enumeration value:: >>> print(repr(Color.red))
Color.red [value=1]
>>> cls = Colors.red.enum The *type* of an enumeration member is the enumeration it belongs to::
>>> print(cls.__name__)
Colors >>> type(Color.red)
<Enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>
Enums also have a property that contains just their item name:: Enums also have a property that contains just their item name::
>>> print(Colors.red.name) >>> print(Color.red.name)
red 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) >>> class Shake(Enum):
<Colors {red: 1, green: 2, blue: 3}> ... vanilla = 7
>>> print(repr(Colors)) ... chocolate = 4
<Colors {red: 1, green: 2, blue: 3}> ... 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:: Enumeration members are hashable, so they can be used in dictionaries and sets::
>>> 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::
>>> apples = {} >>> apples = {}
>>> apples[Colors.red] = 'red delicious' >>> apples[Color.red] = 'red delicious'
>>> apples[Colors.green] = 'granny smith' >>> apples[Color.green] = 'granny smith'
>>> apples >>> apples
{<EnumValue: Colors.green [value=2]>: 'granny smith', <EnumValue: Colors.red [value=1]>: '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. Sometimes it's useful to access members in enumerations programmatically (i.e.
situations where ``Colors.red`` won't do because the exact color is not known situations where ``Color.red`` won't do because the exact color is not known
at program-writing time). ``Enum`` allows such access by value:: at program-writing time). ``Enum`` allows such access::
>>> Colors[1] >>> Color(1)
<EnumValue: Colors.red [value=1]> Color.red [value=1]
>>> Colors[2] >>> Color(3)
<EnumValue: Colors.green [value=2]> Color.blue [value=3]
For consistency, an ``EnumValue`` can be used in the same access pattern:: If you want to access enum members by *name*, ``Enum`` works as expected with
>>> Colors[Colors.red]
<EnumValue: Colors.red [value=1]>
>>> Colors[Colors.green]
<EnumValue: Colors.green [value=2]>
If you want to access enum values by *name*, ``Enum`` works as expected with
``getattr``:: ``getattr``::
>>> getattr(Colors, 'red') >>> getattr(Color, 'red')
<EnumValue: Colors.red [value=1]> Color.red [value=1]
>>> getattr(Colors, 'green') >>> getattr(Color, 'green')
<EnumValue: Colors.green [value=2]> 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 Comparisons
----------- -----------
Enumeration values are compared by identity:: Enumeration members are compared by identity::
>>> Colors.red is Colors.red >>> Color.red is Color.red
True True
>>> Colors.blue is Colors.blue >>> Color.red is Color.blue
True
>>> Colors.red is not Colors.blue
True
>>> Colors.blue is Colors.red
False False
>>> Color.red is not Color.blue
True
Ordered comparisons between enumeration values are *not* supported. Enums are Ordered comparisons between enumeration values are *not* supported. Enums are
not integers (but see `IntEnum`_ below):: not integers (but see `IntEnum`_ below)::
>>> Colors.red < Colors.blue >>> Color.red < Color.blue
Traceback (most recent call last): Traceback (most recent call last):
... File "<stdin>", line 1, in <module>
NotImplementedError TypeError: unorderable types: Color() < Color()
>>> 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
Equality comparisons are defined though:: Equality comparisons are defined though::
>>> Colors.blue == Colors.blue >>> Color.blue == Color.red
True False
>>> Colors.green != Colors.blue >>> Color.blue == Color.blue
True 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 >>> Color.blue == 2
False
>>> Colors.blue == 3
False
>>> Colors.green != 3
True
>>> Colors.green == 'green'
False False
Allowed members and attributs of enumerations
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
------------------
The examples above use integers for enumeration values. Using integers is The examples above use integers for enumeration values. Using integers is
short and handy (and provided by default by the `Convenience API`_), but not 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): >>> class SpecialId(Enum):
... selector = '$IM($N)' ... selector = '$IM($N)'
... adaptor = '~$IM' ... adaptor = '~$IM'
... ...
>>> SpecialId.selector >>> SpecialId.selector
<EnumValue: SpecialId.selector [value=$IM($N)]> SpecialId.selector [value='$IM($N)']
>>> SpecialId.selector.value >>> SpecialId.selector.value
'$IM($N)' '$IM($N)'
>>> a = SpecialId.adaptor >>> a = SpecialId.adaptor
@ -342,8 +264,6 @@ enumerations can have arbitrary values. The following example uses strings::
True True
>>> print(a) >>> print(a)
SpecialId.adaptor SpecialId.adaptor
>>> print(a.value)
~$IM
Here ``Enum`` is used to provide readable (and syntactically valid!) names for Here ``Enum`` is used to provide readable (and syntactically valid!) names for
some special values, as well as group them together. 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 enumerations aren't important and enumerations are just used for their
naming and comparison properties. 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 IntEnum
------- -------
A variation of ``Enum`` is proposed where the enumeration values also A variation of ``Enum`` is proposed which is also a subclass of ``int``.
subclasses ``int`` - ``IntEnum``. These values can be compared to Members of an ``IntEnum`` can be compared to integers; by extension,
integers; by extension, enumerations of different types can also be integer enumerations of different types can also be compared to each other::
compared to each other::
>>> from enum import IntEnum >>> from enum import IntEnum
>>> class Shape(IntEnum): >>> class Shape(IntEnum):
@ -384,11 +363,11 @@ However they still can't be compared to ``Enum``::
... circle = 1 ... circle = 1
... square = 2 ... square = 2
... ...
>>> class Colors(Enum): >>> class Color(Enum):
... red = 1 ... red = 1
... green = 2 ... green = 2
... ...
>>> Shape.circle == Colors.red >>> Shape.circle == Color.red
False False
``IntEnum`` values behave like integers in other ways you'd expect:: ``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:: The ``Enum`` class is callable, providing the following convenience API::
>>> Animals = Enum('Animals', 'ant bee cat dog') >>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animals >>> Animal
<Animals {ant: 1, bee: 2, cat: 3, dog: 4}> <Enum 'Animal'>
>>> Animals.ant >>> Animal.ant
<EnumValue: Animals.ant [value=1]> Animal.ant [value=1]
>>> Animals.ant.value >>> Animal.ant.value
1 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 semantics of this API resemble ``namedtuple``. The first argument of
the call to ``Enum`` is the name of the enumeration. The second argument is 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. 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 The last option enables assigning arbitrary values to enumerations; the others
auto-assign increasing integers starting with 1. A new class derived from 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:: equivalent to::
>>> class Animals(Enum): >>> class Animals(Enum):
@ -448,26 +429,22 @@ equivalent to::
... cat = 3 ... cat = 3
... dog = 4 ... dog = 4
Examples of alternative name/value specifications::
>>> Enum('Animals', ['ant', 'bee', 'cat', 'dog'])
<Animals {ant: 1, bee: 2, cat: 3, dog: 4}>
>>> Enum('Animals', (('ant', 'one'), ('bee', 'two'), ('cat', 'three'), ('dog', 'four')))
<Animals {dog: four, ant: one, cat: three, bee: two}>
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)
<Levels {debug: 10, info: 20, warning: 30, severe: 40}>
Proposed variations Proposed variations
=================== ===================
Some variations were proposed during the discussions in the mailing list. Some variations were proposed during the discussions in the mailing list.
Here's some of the more popular ones. 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 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 definition of such enums baffling when first seen. Besides, explicit is
better than implicit. better than implicit.
Using special names or forms to auto-assign enum values 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 Cons: actually longer to type in many simple cases. The argument of explicit
vs. implicit applies here as well. vs. implicit applies here as well.
Use-cases in the standard library 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 about a lot of networking code (especially implementation of protocols) and
can be seen in test protocols written with the Tulip library as well. 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 Acknowledgments
=============== ===============
This PEP describes the ``flufl.enum`` package by Barry Warsaw. ``flufl.enum`` This PEP was initially proposing including the ``flufl.enum`` package [8]_
is based on an example by Jeremy Hylton. It has been modified and extended by Barry Warsaw into the stdlib, and is inspired in large parts by it.
by Barry Warsaw for use in the GNU Mailman [5]_ project. Ben Finney is the Ben Finney is the author of the earlier enumeration PEP 354.
author of the earlier enumeration PEP 354.
References References
========== ==========
.. [1] http://pythonhosted.org/flufl.enum/docs/using.html .. [1] Placeholder for pronouncement
.. [2] http://www.python.org/dev/peps/pep-0354/ .. [2] http://www.python.org/dev/peps/pep-0354/
.. [3] http://mail.python.org/pipermail/python-ideas/2013-January/019003.html .. [3] http://mail.python.org/pipermail/python-ideas/2013-January/019003.html
.. [4] http://mail.python.org/pipermail/python-ideas/2013-February/019373.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 Copyright
========= =========
@ -608,7 +556,8 @@ This document has been placed in the public domain.
Todo 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: Local Variables: