<div dir="ltr"><div><div><div><div><div>Hello python-dev,<br><br></div>We're happy to present the revised PEP 435, collecting valuable feedback from python-ideas discussions as well as in-person discussions and decisions made during the latest PyCon language summit. We believe the proposal is now better than the original one, providing both a wider set of features and more convenient ways to use those features.<br>
<br></div>Link to the PEP: <a href="http://www.python.org/dev/peps/pep-0435/">http://www.python.org/dev/peps/pep-0435/</a> [it's also pasted fully below for convenience].<br><br></div>Reference implementation is available as the recently released flufl.enum version 4.0 - you can get it either from PyPi or <a href="https://launchpad.net/flufl.enum">https://launchpad.net/flufl.enum</a>. flufl.enum 4.0 was developed in parallel with revising PEP 435.<br>
<br></div>Comments welcome,<br><br></div>Barry and Eli<br><br>----------------------------------<br><br>PEP: 435<br>Title: Adding an Enum type to the Python standard library<br>Version: $Revision$<br>Last-Modified: $Date$<br>
Author: Barry Warsaw <<a href="mailto:barry@python.org">barry@python.org</a>>,<br> Eli Bendersky <<a href="mailto:eliben@gmail.com">eliben@gmail.com</a>><br>Status: Draft<br>Type: Standards Track<br>Content-Type: text/x-rst<br>
Created: 2013-02-23<br>Python-Version: 3.4<br>Post-History: 2013-02-23<br><br><br>Abstract<br>========<br><br>This PEP proposes adding an enumeration type to the Python standard library.<br>Specifically, it proposes moving the existing ``flufl.enum`` package by Barry<br>
Warsaw into the standard library. Much of this PEP is based on the "using"<br>[1]_ document from the documentation of ``flufl.enum``.<br><br>An enumeration is a set of symbolic names bound to unique, constant values.<br>
Within an enumeration, the values can be compared by identity, and the<br>enumeration itself can be iterated over.<br><br><br>Decision<br>========<br><br>TODO: update decision here once pronouncement is made.<br><br><br>
Status of discussions<br>
=====================<br><br>The idea of adding an enum type to Python is not new - PEP 354 [2]_ is a<br>previous attempt that was rejected in 2005. Recently a new set of discussions<br>was initiated [3]_ on the ``python-ideas`` mailing list. Many new ideas were<br>
proposed in several threads; after a lengthy discussion Guido proposed adding<br>``flufl.enum`` to the standard library [4]_. During the PyCon 2013 language<br>summit the issue was discussed further. It became clear that many developers<br>
want to see an enum that subclasses ``int``, which can allow us to replace<br>many integer constants in the standard library by enums with friendly string<br>representations, without ceding backwards compatibility. An additional<br>
discussion among several interested core developers led to the proposal of<br>having ``IntEnum`` as a special case of ``Enum``.<br><br>The key dividing issue between ``Enum`` and ``IntEnum`` is whether comparing<br>to integers is semantically meaningful. For most uses of enumerations, it's<br>
a **feature** to reject comparison to integers; enums that compare to integers<br>lead, through transitivity, to comparisons between enums of unrelated types,<br>which isn't desirable in most cases. For some uses, however, greater<br>
interoperatiliby with integers is desired. For instance, this is the case for<br>replacing existing standard library constants (such as ``socket.AF_INET``)<br>with enumerations.<br><br>This PEP is an attempt to formalize this decision as well as discuss a number<br>
of variations that were discussed and can be considered for inclusion.<br><br><br>Motivation<br>==========<br><br>*[Based partly on the Motivation stated in PEP 354]*<br><br>The properties of an enumeration are useful for defining an immutable, related<br>
set of constant values that have a defined sequence but no inherent semantic<br>meaning. Classic examples are days of the week (Sunday through Saturday) and<br>school assessment grades ('A' through 'D', and 'F'). Other examples include<br>
error status values and states within a defined process.<br><br>It is possible to simply define a sequence of values of some other basic type,<br>such as ``int`` or ``str``, to represent discrete arbitrary values. However,<br>
an enumeration ensures that such values are distinct from any others including,<br>importantly, values within other enumerations, and that operations without<br>meaning ("Wednesday times two") are not defined for these values. It also<br>
provides a convenient printable representation of enum values without requiring<br>tedious repetition while defining them (i.e. no ``GREEN = 'green'``).<br><br><br>Module and type name<br>====================<br>
<br>
We propose to add a module named ``enum`` to the standard library. The main<br>type exposed by this module is ``Enum``. Hence, to import the ``Enum`` type<br>user code will run::<br><br> >>> from enum import Enum<br>
<br><br>Proposed semantics for the new enumeration type<br>===============================================<br><br>Creating an Enum<br>----------------<br><br>Enumerations are created using the class syntax, which makes them easy to read<br>
and write. An alternative creation method is described in `Convenience API`_.<br>To define an enumeration, derive from the ``Enum`` class and add attributes<br>with assignment to their integer values::<br><br> >>> from enum import Enum<br>
>>> class Colors(Enum):<br> ... red = 1<br> ... green = 2<br> ... blue = 3<br><br>Enumeration values have nice, human readable string representations::<br><br> >>> print(Colors.red)<br>
Colors.red<br><br>...while their repr has more information::<br><br> >>> print(repr(Colors.red))<br> <EnumValue: Colors.red [value=1]><br><br>The enumeration value names are available through the class members::<br>
<br> >>> for member in Colors.__members__:<br> ... print(member)<br> red<br> green<br> blue<br><br>Let's say you wanted to encode an enumeration value in a database. You might<br>want to get the enumeration class object from an enumeration value::<br>
<br> >>> cls = Colors.red.enum<br> >>> print(cls.__name__)<br> Colors<br><br>Enums also have a property that contains just their item name::<br><br> >>> print(<a href="http://Colors.red.name">Colors.red.name</a>)<br>
red<br> >>> print(<a href="http://Colors.green.name">Colors.green.name</a>)<br> green<br> >>> print(<a href="http://Colors.blue.name">Colors.blue.name</a>)<br> blue<br><br>The str and repr of the enumeration class also provides useful information::<br>
<br> >>> print(Colors)<br> <Colors {red: 1, green: 2, blue: 3}><br> >>> print(repr(Colors))<br> <Colors {red: 1, green: 2, blue: 3}><br><br>The ``Enum`` class supports iteration. Iteration is defined as the<br>
sorted order of the item values::<br><br> >>> class FiveColors(Enum):<br> ... pink = 4<br> ... cyan = 5<br> ... green = 2<br> ... blue = 3<br> ... red = 1<br> >>> [<a href="http://v.name">v.name</a> for v in FiveColors]<br>
['red', 'green', 'blue', 'pink', 'cyan']<br><br>Enumeration values are hashable, so they can be used in dictionaries and sets::<br><br> >>> apples = {}<br> >>> apples[Colors.red] = 'red delicious'<br>
>>> apples[Colors.green] = 'granny smith'<br> >>> apples<br> {<EnumValue: Colors.green [value=2]>: 'granny smith', <EnumValue: Colors.red [value=1]>: 'red delicious'}<br>
<br>To programmatically access enumeration values, use ``getattr``::<br><br> >>> getattr(Colors, 'red')<br> <EnumValue: Colors.red [value=1]><br><br>Comparisons<br>-----------<br><br>Enumeration values are compared by identity::<br>
<br> >>> Colors.red is Colors.red<br> True<br> >>> Colors.blue is Colors.blue<br> True<br> >>> Colors.red is not Colors.blue<br> True<br> >>> Colors.blue is Colors.red<br>
False<br><br>Ordered comparisons between enumeration values are *not* supported. Enums are<br>not integers (but see `IntEnum`_ below)::<br><br> >>> Colors.red < Colors.blue<br> Traceback (most recent call last):<br>
...<br> NotImplementedError<br> >>> Colors.red <= Colors.blue<br> Traceback (most recent call last):<br> ...<br> NotImplementedError<br> >>> Colors.blue > Colors.green<br> Traceback (most recent call last):<br>
...<br> NotImplementedError<br> >>> Colors.blue >= Colors.green<br> Traceback (most recent call last):<br> ...<br> NotImplementedError<br><br>Equality comparisons are defined though::<br><br>
>>> Colors.blue == Colors.blue<br> True<br> >>> Colors.green != Colors.blue<br> True<br><br>Comparisons against non-enumeration values will always compare not equal::<br><br> >>> Colors.green == 2<br>
False<br> >>> Colors.blue == 3<br> False<br> >>> Colors.green != 3<br> True<br> >>> Colors.green == 'green'<br> False<br><br><br>Extending enumerations by subclassing<br>
-------------------------------------<br><br>You can extend previously defined Enums by subclassing::<br><br> >>> class MoreColors(Colors):<br> ... pink = 4<br> ... cyan = 5<br><br>When extended in this way, the base enumeration's values are identical to the<br>
same named values in the derived class::<br><br> >>> Colors.red is MoreColors.red<br> True<br> >>> Colors.blue is MoreColors.blue<br> True<br><br>However, these are not doing comparisons against the integer<br>
equivalent values, because if you define an enumeration with similar<br>item names and integer values, they will not be identical::<br><br> >>> class OtherColors(Enum):<br> ... red = 1<br> ... blue = 2<br>
... yellow = 3<br> >>> Colors.red is OtherColors.red<br> False<br> >>> Colors.blue is not OtherColors.blue<br> True<br><br>These enumeration values are not equal, nor do they and hence may exist<br>
in the same set, or as distinct keys in the same dictionary::<br><br> >>> Colors.red == OtherColors.red<br> False<br> >>> len(set((Colors.red, OtherColors.red)))<br> 2<br><br>You may not define two enumeration values with the same integer value::<br>
<br> >>> class Bad(Enum):<br> ... cartman = 1<br> ... stan = 2<br> ... kyle = 3<br> ... kenny = 3 # Oops!<br> ... butters = 4<br> Traceback (most recent call last):<br> ...<br>
ValueError: Conflicting enums with value '3': 'kenny' and 'kyle'<br><br>You also may not duplicate values in derived enumerations::<br><br> >>> class BadColors(Colors):<br> ... yellow = 4<br>
... chartreuse = 2 # Oops!<br> Traceback (most recent call last):<br> ...<br> ValueError: Conflicting enums with value '2': 'green' and 'chartreuse'<br><br><br>Enumeration values<br>
------------------<br><br>The examples above use integers for enumeration values. Using integers is<br>short and handy (and provided by default by the `Convenience API`_), but not<br>strictly enforced. In the vast majority of use-cases, one doesn't care what<br>
the actual value of an enumeration is. But if the value *is* important,<br>enumerations can have arbitrary values. The following example uses strings::<br><br> >>> class SpecialId(Enum):<br> ... selector = '$IM($N)'<br>
... adaptor = '~$IM'<br> ...<br> >>> SpecialId.selector<br> <EnumValue: SpecialId.selector [value=$IM($N)]><br> >>> SpecialId.selector.value<br> '$IM($N)'<br> >>> a = SpecialId.adaptor<br>
>>> a == '~$IM'<br> False<br> >>> a == SpecialId.adaptor<br> True<br> >>> print(a)<br> SpecialId.adaptor<br> >>> print(a.value)<br> ~$IM<br><br>Here ``Enum`` is used to provide readable (and syntactically valid!) names for<br>
some special values, as well as group them together.<br><br>While ``Enum`` supports this flexibility, one should only use it in<br>very special cases. Code will be most readable when actual values of<br>enumerations aren't important and enumerations are just used for their<br>
naming and comparison properties.<br><br><br>IntEnum<br>-------<br><br>A variation of ``Enum`` is proposed where the enumeration values also<br>subclasses ``int`` - ``IntEnum``. These values can be compared to<br>integers; by extension, enumerations of different types can also be<br>
compared to each other::<br><br> >>> from enum import IntEnum<br> >>> class Shape(IntEnum):<br> ... circle = 1<br> ... square = 2<br> ...<br> >>> class Request(IntEnum):<br>
... post = 1<br> ... get = 2<br> ...<br> >>> Shape == 1<br> False<br> >>> Shape.circle == 1<br> True<br> >>> Shape.circle == Request.post<br> True<br><br>However they still can't be compared to ``Enum``::<br>
<br> >>> class Shape(IntEnum):<br> ... circle = 1<br> ... square = 2<br> ...<br> >>> class Colors(Enum):<br> ... red = 1<br> ... green = 2<br> ...<br> >>> Shape.circle == Colors.red<br>
False<br><br>``IntEnum`` values behave like integers in other ways you'd expect::<br><br> >>> int(Shape.circle)<br> 1<br> >>> ['a', 'b', 'c'][Shape.circle]<br> 'b'<br>
>>> [i for i in range(Shape.square)]<br> [0, 1]<br><br>For the vast majority of code, ``Enum`` is strongly recommended.<br>Since ``IntEnum`` breaks some semantic promises of an enumeration (by<br>being comparable to integers, and thus by transitivity to other<br>
unrelated enumerations), it should be used only in special cases where<br>there's no other choice; for example, when integer constants are<br>replaced with enumerations and backwards compatibility is required<br>with code that still expects integers.<br>
<br><br>Pickling<br>--------<br><br>Enumerations created with the class syntax can also be pickled and unpickled::<br><br> >>> from enum.tests.fruit import Fruit<br> >>> from pickle import dumps, loads<br>
>>> Fruit.tomato is loads(dumps(Fruit.tomato))<br> True<br><br><br>Convenience API<br>---------------<br><br>The ``Enum`` class is callable, providing the following convenience API::<br><br> >>> Animals = Enum('Animals', 'ant bee cat dog')<br>
>>> Animals<br> <Animals {ant: 1, bee: 2, cat: 3, dog: 4}><br> >>> Animals.ant<br> <EnumValue: Animals.ant [value=1]><br> >>> Animals.ant.value<br> 1<br><br>The semantics of this API resemble ``namedtuple``. The first argument of<br>
the call to ``Enum`` is the name of the enumeration. The second argument is<br>a source of enumeration value names. It can be a whitespace-separated string<br>of names, a sequence of names or a sequence of 2-tuples with key/value pairs.<br>
The last option enables assigning arbitrary values to enumerations; the others<br>auto-assign increasing integers starting with 1. A new class derived from<br>``Enum`` is returned. In other words, the above assignment to ``Animals`` is<br>
equivalent to::<br><br> >>> class Animals(Enum):<br> ... ant = 1<br> ... bee = 2<br> ... cat = 3<br> ... dog = 4<br><br>Examples of alternative name/value specifications::<br><br> >>> Enum('Animals', ['ant', 'bee', 'cat', 'dog'])<br>
<Animals {ant: 1, bee: 2, cat: 3, dog: 4}><br> >>> Enum('Animals', (('ant', 'one'), ('bee', 'two'), ('cat', 'three'), ('dog', 'four')))<br>
<Animals {dog: four, ant: one, cat: three, bee: two}><br><br>The second argument can also be a dictionary mapping names to values::<br><br> >>> levels = dict(debug=10, info=20, warning=30, severe=40)<br>
>>> Enum('Levels', levels)<br> <Levels {debug: 10, info: 20, warning: 30, severe: 40}><br><br><br>Proposed variations<br>===================<br><br>Some variations were proposed during the discussions in the mailing list.<br>
Here's some of the more popular ones.<br><br><br>Not having to specify values for enums<br>--------------------------------------<br><br>Michael Foord proposed (and Tim Delaney provided a proof-of-concept<br>implementation) to use metaclass magic that makes this possible::<br>
<br> class Color(Enum):<br> red, green, blue<br><br>The values get actually assigned only when first looked up.<br><br>Pros: cleaner syntax that requires less typing for a very common task (just<br>listing enumeration names without caring about the values).<br>
<br>Cons: involves much magic in the implementation, which makes even the<br>definition of such enums baffling when first seen. Besides, explicit is<br>better than implicit.<br><br><br>Using special names or forms to auto-assign enum values<br>
-------------------------------------------------------<br><br>A different approach to avoid specifying enum values is to use a special name<br>or form to auto assign them. For example::<br><br> class Color(Enum):<br>
red = None # auto-assigned to 0<br> green = None # auto-assigned to 1<br> blue = None # auto-assigned to 2<br><br>More flexibly::<br><br> class Color(Enum):<br> red = 7<br>
green = None # auto-assigned to 8<br> blue = 19<br> purple = None # auto-assigned to 20<br><br>Some variations on this theme:<br><br>#. A special name ``auto`` imported from the enum package.<br>
#. Georg Brandl proposed ellipsis (``...``) instead of ``None`` to achieve the<br> same effect.<br><br>Pros: no need to manually enter values. Makes it easier to change the enum and<br>extend it, especially for large enumerations.<br>
<br>Cons: actually longer to type in many simple cases. The argument of explicit<br>vs. implicit applies here as well.<br><br><br>Use-cases in the standard library<br>=================================<br><br>The Python standard library has many places where the usage of enums would be<br>
beneficial to replace other idioms currently used to represent them. Such<br>usages can be divided to two categories: user-code facing constants, and<br>internal constants.<br><br>User-code facing constants like ``os.SEEK_*``, ``socket`` module constants,<br>
decimal rounding modes and HTML error codes could require backwards<br>compatibility since user code may expect integers. ``IntEnum`` as described<br>above provides the required semantics; being a subclass of ``int``, it does not<br>
affect user code that expects integers, while on the other hand allowing<br>printable representations for enumeration values::<br><br> >>> import socket<br> >>> family = socket.AF_INET<br> >>> family == 2<br>
True<br> >>> print(family)<br> SocketFamily.AF_INET<br><br>Internal constants are not seen by user code but are employed internally by<br>stdlib modules. These can be implemented with ``Enum``. Some examples<br>
uncovered by a very partial skim through the stdlib: ``binhex``, ``imaplib``,<br>``http/client``, ``urllib/robotparser``, ``idlelib``, ``concurrent.futures``,<br>``turtledemo``.<br><br>In addition, looking at the code of the Twisted library, there are many use<br>
cases for replacing internal state constants with enums. The same can be said<br>about a lot of networking code (especially implementation of protocols) and<br>can be seen in test protocols written with the Tulip library as well.<br>
<br><br>Differences from PEP 354<br>========================<br><br>Unlike PEP 354, enumeration values are not defined as a sequence of strings,<br>but as attributes of a class. This design was chosen because it was felt that<br>
class syntax is more readable.<br><br>Unlike PEP 354, enumeration values require an explicit integer value. This<br>difference recognizes that enumerations often represent real-world values, or<br>must interoperate with external real-world systems. For example, to store an<br>
enumeration in a database, it is better to convert it to an integer on the way<br>in and back to an enumeration on the way out. Providing an integer value also<br>provides an explicit ordering. However, there is no automatic conversion to<br>
and from the integer values, because explicit is better than implicit.<br><br>Unlike PEP 354, this implementation does use a metaclass to define the<br>enumeration's syntax, and allows for extended base-enumerations so that the<br>
common values in derived classes are identical (a singleton model). While PEP<br>354 dismisses this approach for its complexity, in practice any perceived<br>complexity, though minimal, is hidden from users of the enumeration.<br>
<br>Unlike PEP 354, enumeration values should only be tested by identity<br>comparison. This is to emphasize the fact that enumeration values are<br>singletons, much like ``None``.<br><br><br>Acknowledgments<br>===============<br>
<br>This PEP describes the ``flufl.enum`` package by Barry Warsaw. ``flufl.enum``<br>is based on an example by Jeremy Hylton. It has been modified and extended<br>by Barry Warsaw for use in the GNU Mailman [5]_ project. Ben Finney is the<br>
author of the earlier enumeration PEP 354.<br><br><br>References<br>==========<br><br>.. [1] <a href="http://pythonhosted.org/flufl.enum/docs/using.html">http://pythonhosted.org/flufl.enum/docs/using.html</a><br>.. [2] <a href="http://www.python.org/dev/peps/pep-0354/">http://www.python.org/dev/peps/pep-0354/</a><br>
.. [3] <a href="http://mail.python.org/pipermail/python-ideas/2013-January/019003.html">http://mail.python.org/pipermail/python-ideas/2013-January/019003.html</a><br>.. [4] <a href="http://mail.python.org/pipermail/python-ideas/2013-February/019373.html">http://mail.python.org/pipermail/python-ideas/2013-February/019373.html</a><br>
.. [5] <a href="http://www.list.org">http://www.list.org</a><br><br><br>Copyright<br>=========<br><br>This document has been placed in the public domain.<br><br><br>Todo<br>====<br><br> * Mark PEP 354 "superseded by" this one, if accepted<br>
<br>..<br> Local Variables:<br> mode: indented-text<br> indent-tabs-mode: nil<br> sentence-end-double-space: t<br> fill-column: 70<br> coding: utf-8<br> End:<br><br><div><div><div><div><div><div><div><br></div>
</div></div></div></div></div></div></div>