[Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

Antoine Pitrou solipsis at pitrou.net
Tue Apr 23 15:42:40 CEST 2013


Hello,

Le Fri, 12 Apr 2013 05:55:00 -0700,
Eli Bendersky <eliben at gmail.com> a écrit :
> 
> 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.

I'm having a problem with the proposed implementation. I haven't found
any mention of it, so apologies if this has already been discussed:


> 
> Link to the PEP: http://www.python.org/dev/peps/pep-0435/ [it's also
> pasted fully below for convenience].
> 
> Reference implementation is available as the recently released
> flufl.enum version 4.0 - you can get it either from PyPi or
> https://launchpad.net/flufl.enum. flufl.enum 4.0 was developed in
> parallel with revising PEP 435.
> 
> Comments welcome,
> 
> Barry and Eli
> 
> ----------------------------------
> 
> PEP: 435
> Title: Adding an Enum type to the Python standard library
> Version: $Revision$
> Last-Modified: $Date$
> Author: Barry Warsaw <barry at python.org>,
>         Eli Bendersky <eliben at gmail.com>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 2013-02-23
> Python-Version: 3.4
> Post-History: 2013-02-23
> 
> 
> 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 enumeration itself can be iterated over.
> 
> 
> Decision
> ========
> 
> TODO: update decision here once pronouncement is made.
> 
> 
> Status of discussions
> =====================
> 
> The idea of adding an enum type to Python is not new - PEP 354 [2]_
> is a previous attempt that was rejected in 2005.  Recently a new set
> of discussions
> was initiated [3]_ on the ``python-ideas`` mailing list.  Many new
> ideas were
> proposed in several threads; after a lengthy discussion Guido proposed
> adding
> ``flufl.enum`` to the standard library [4]_.  During the PyCon 2013
> language summit the issue was discussed further.  It became clear
> that many developers
> want to see an enum that subclasses ``int``, which can allow us to
> replace many integer constants in the standard library by enums with
> friendly string representations, without ceding backwards
> compatibility.  An additional discussion among several interested
> core developers led to the proposal of having ``IntEnum`` as a
> special case of ``Enum``.
> 
> The key dividing issue between ``Enum`` and ``IntEnum`` is whether
> comparing to integers is semantically meaningful.  For most uses of
> enumerations, it's a **feature** to reject comparison to integers;
> enums that compare to integers
> lead, through transitivity, to comparisons between enums of unrelated
> types, which isn't desirable in most cases.  For some uses, however,
> greater 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 discussed and can be considered for inclusion.
> 
> 
> Motivation
> ==========
> 
> *[Based partly on the Motivation stated in PEP 354]*
> 
> The properties of an enumeration are useful for defining an immutable,
> related
> set of constant values that have a defined sequence but no inherent
> semantic meaning.  Classic examples are days of the week (Sunday
> through Saturday) and
> school assessment grades ('A' through 'D', and 'F').  Other examples
> include error status values and states within a defined process.
> 
> It is possible to simply define a sequence of values of some other
> basic type,
> such as ``int`` or ``str``, to represent discrete arbitrary values.
> However,
> an enumeration ensures that such values are distinct from any others
> including,
> importantly, values within other enumerations, and that operations
> without meaning ("Wednesday times two") are not defined for these
> values.  It also provides a convenient printable representation of
> enum values without requiring
> tedious repetition while defining them (i.e. no ``GREEN = 'green'``).
> 
> 
> Module and type name
> ====================
> 
> We propose to add a module named ``enum`` to the standard library.
> The main type exposed by this module is ``Enum``.  Hence, to import
> the ``Enum`` type user code will run::
> 
>     >>> from enum import Enum
> 
> 
> Proposed semantics for the new enumeration type
> ===============================================
> 
> 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::
> 
>     >>> from enum import Enum
>     >>> class Colors(Enum):
>     ...     red = 1
>     ...     green = 2
>     ...     blue = 3
> 
> Enumeration values have nice, human readable string representations::
> 
>     >>> print(Colors.red)
>     Colors.red
> 
> ...while their repr has more information::
> 
>     >>> print(repr(Colors.red))
>     <EnumValue: Colors.red [value=1]>
> 
> The enumeration value names are available through the class members::
> 
>     >>> for member in Colors.__members__:
>     ...     print(member)
>     red
>     green
>     blue
> 
> Let's say you wanted to encode an enumeration value in a database.
> You might
> want to get the enumeration class object from an enumeration value::
> 
>     >>> cls = Colors.red.enum
>     >>> print(cls.__name__)
>     Colors
> 
> Enums also have a property that contains just their item name::
> 
>     >>> print(Colors.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::
> 
>     >>> print(Colors)
>     <Colors {red: 1, green: 2, blue: 3}>
>     >>> print(repr(Colors))
>     <Colors {red: 1, green: 2, blue: 3}>
> 
> The ``Enum`` class supports iteration.  Iteration is defined as the
> sorted order of the item values::
> 
>     >>> class FiveColors(Enum):
>     ...     pink = 4
>     ...     cyan = 5
>     ...     green = 2
>     ...     blue = 3
>     ...     red = 1
>     >>> [v.name for v in FiveColors]
>     ['red', 'green', 'blue', 'pink', 'cyan']
> 
> Enumeration values are hashable, so they can be used in dictionaries
> and sets::
> 
>     >>> apples = {}
>     >>> apples[Colors.red] = 'red delicious'
>     >>> apples[Colors.green] = 'granny smith'
>     >>> apples
>     {<EnumValue: Colors.green [value=2]>: 'granny smith', <EnumValue:
> Colors.red [value=1]>: 'red delicious'}
> 
> To programmatically access enumeration values, use ``getattr``::
> 
>     >>> getattr(Colors, 'red')
>     <EnumValue: Colors.red [value=1]>
> 
> Comparisons
> -----------
> 
> Enumeration values are compared by identity::
> 
>     >>> Colors.red is Colors.red
>     True
>     >>> Colors.blue is Colors.blue
>     True
>     >>> Colors.red is not Colors.blue
>     True
>     >>> Colors.blue is Colors.red
>     False
> 
> Ordered comparisons between enumeration values are *not* supported.
> Enums are
> not integers (but see `IntEnum`_ below)::
> 
>     >>> Colors.red < Colors.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
> 
> Equality comparisons are defined though::
> 
>     >>> Colors.blue == Colors.blue
>     True
>     >>> Colors.green != Colors.blue
>     True
> 
> Comparisons against non-enumeration values will always compare not
> equal::
> 
>     >>> Colors.green == 2
>     False
>     >>> Colors.blue == 3
>     False
>     >>> Colors.green != 3
>     True
>     >>> Colors.green == 'green'
>     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
> 
> These enumeration values are not equal, nor do they and hence 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 short and handy (and provided by default by the
> `Convenience API`_), but not strictly enforced.  In the vast majority
> of use-cases, one doesn't care what the actual value of an
> enumeration is.  But if the value *is* important, enumerations can
> have arbitrary values.  The following example uses strings::
> 
>     >>> class SpecialId(Enum):
>     ...   selector = '$IM($N)'
>     ...   adaptor = '~$IM'
>     ...
>     >>> SpecialId.selector
>     <EnumValue: SpecialId.selector [value=$IM($N)]>
>     >>> SpecialId.selector.value
>     '$IM($N)'
>     >>> a = SpecialId.adaptor
>     >>> a == '~$IM'
>     False
>     >>> a == SpecialId.adaptor
>     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.
> 
> While ``Enum`` supports this flexibility, one should only use it in
> 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.
> 
> 
> 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::
> 
>     >>> 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
> 
> However they still can't be compared to ``Enum``::
> 
>     >>> class Shape(IntEnum):
>     ...   circle = 1
>     ...   square = 2
>     ...
>     >>> class Colors(Enum):
>     ...   red = 1
>     ...   green = 2
>     ...
>     >>> Shape.circle == Colors.red
>     False
> 
> ``IntEnum`` values behave like integers in other ways you'd expect::
> 
>     >>> int(Shape.circle)
>     1
>     >>> ['a', 'b', 'c'][Shape.circle]
>     'b'
>     >>> [i for i in range(Shape.square)]
>     [0, 1]
> 
> For the vast majority of code, ``Enum`` is strongly recommended.
> Since ``IntEnum`` breaks some semantic promises of an enumeration (by
> being comparable to integers, and thus by transitivity to other
> unrelated enumerations), it should be used only in special cases where
> there's no other choice; for example, when integer constants are
> replaced with enumerations and backwards compatibility is required
> with code that still expects integers.
> 
> 
> Pickling
> --------
> 
> Enumerations created with the class syntax can also be pickled and
> unpickled::
> 
>     >>> from enum.tests.fruit import Fruit
>     >>> from pickle import dumps, loads
>     >>> Fruit.tomato is loads(dumps(Fruit.tomato))
>     True
> 
> 
> Convenience API
> ---------------
> 
> The ``Enum`` class is callable, providing the following convenience
> API::
> 
>     >>> Animals = Enum('Animals', 'ant bee cat dog')
>     >>> Animals
>     <Animals {ant: 1, bee: 2, cat: 3, dog: 4}>
>     >>> Animals.ant
>     <EnumValue: Animals.ant [value=1]>
>     >>> Animals.ant.value
>     1
> 
> 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
> 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
> equivalent to::
> 
>     >>> class Animals(Enum):
>     ...   ant = 1
>     ...   bee = 2
>     ...   cat = 3
>     ...   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
> ===================
> 
> Some variations were proposed during the discussions in the mailing
> list. Here's some of the more popular ones.
> 
> 
> Not having to specify values for enums
> --------------------------------------
> 
> Michael Foord proposed (and Tim Delaney provided a proof-of-concept
> implementation) to use metaclass magic that makes this possible::
> 
>     class Color(Enum):
>         red, green, blue
> 
> The values get actually assigned only when first looked up.
> 
> Pros: cleaner syntax that requires less typing for a very common task
> (just listing enumeration names without caring about the values).
> 
> 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
> -------------------------------------------------------
> 
> A different approach to avoid specifying enum values is to use a
> special name
> or form to auto assign them.  For example::
> 
>     class Color(Enum):
>         red = None          # auto-assigned to 0
>         green = None        # auto-assigned to 1
>         blue = None         # auto-assigned to 2
> 
> More flexibly::
> 
>     class Color(Enum):
>         red = 7
>         green = None        # auto-assigned to 8
>         blue = 19
>         purple = None       # auto-assigned to 20
> 
> Some variations on this theme:
> 
> #. A special name ``auto`` imported from the enum package.
> #. Georg Brandl proposed ellipsis (``...``) instead of ``None`` to
> achieve the
>    same effect.
> 
> Pros: no need to manually enter values. Makes it easier to change the
> enum and
> 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
> =================================
> 
> The Python standard library has many places where the usage of enums
> would be
> beneficial to replace other idioms currently used to represent them.
> Such usages can be divided to two categories: user-code facing
> constants, and internal constants.
> 
> User-code facing constants like ``os.SEEK_*``, ``socket`` module
> constants, decimal rounding modes and HTML error codes could require
> backwards compatibility since user code may expect integers.
> ``IntEnum`` as described above provides the required semantics; being
> a subclass of ``int``, it does not
> affect user code that expects integers, while on the other hand
> allowing printable representations for enumeration values::
> 
>     >>> import socket
>     >>> family = socket.AF_INET
>     >>> family == 2
>     True
>     >>> print(family)
>     SocketFamily.AF_INET
> 
> Internal constants are not seen by user code but are employed
> internally by stdlib modules.  These can be implemented with
> ``Enum``.  Some examples uncovered by a very partial skim through the
> stdlib: ``binhex``, ``imaplib``,
> ``http/client``, ``urllib/robotparser``, ``idlelib``,
> ``concurrent.futures``,
> ``turtledemo``.
> 
> In addition, looking at the code of the Twisted library, there are
> many use 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.
> 
> 
> References
> ==========
> 
> .. [1] http://pythonhosted.org/flufl.enum/docs/using.html
> .. [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
> 
> 
> Copyright
> =========
> 
> This document has been placed in the public domain.
> 
> 
> Todo
> ====
> 
>  * Mark PEP 354 "superseded by" this one, if accepted
> 
> ..
>    Local Variables:
>    mode: indented-text
>    indent-tabs-mode: nil
>    sentence-end-double-space: t
>    fill-column: 70
>    coding: utf-8
>    End:
> 




More information about the Python-Dev mailing list