[Python-checkins] peps: Overhaul PEP 435 to describe the current state of decisions, which deviates

eli.bendersky python-checkins at python.org
Thu May 2 14:52:21 CEST 2013


http://hg.python.org/peps/rev/f924a0a3da3b
changeset:   4869:f924a0a3da3b
user:        Eli Bendersky <eliben at gmail.com>
date:        Thu May 02 05:51:33 2013 -0700
summary:
  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.

files:
  pep-0435.txt |  459 +++++++++++++++++---------------------
  1 files changed, 204 insertions(+), 255 deletions(-)


diff --git a/pep-0435.txt b/pep-0435.txt
--- a/pep-0435.txt
+++ b/pep-0435.txt
@@ -3,22 +3,20 @@
 Version: $Revision$
 Last-Modified: $Date$
 Author: Barry Warsaw <barry at python.org>,
-        Eli Bendersky <eliben at gmail.com>
+        Eli Bendersky <eliben at gmail.com>,
+        Ethan Furman <ethan at stoneleaf.us>
 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 @@
 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 @@
 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 @@
 
 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))
-    <EnumValue: Colors.red [value=1]>
+...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)
+    <Enum 'Color'>
+    >>> 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)
-    <Colors {red: 1, green: 2, blue: 3}>
-    >>> print(repr(Colors))
-    <Colors {red: 1, green: 2, blue: 3}>
+    >>> 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
-    {<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.
-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]
-    <EnumValue: Colors.red [value=1]>
-    >>> Colors[2]
-    <EnumValue: Colors.green [value=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]
-    <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
+If you want to access enum members by *name*, ``Enum`` works as expected with
 ``getattr``::
 
-    >>> getattr(Colors, 'red')
-    <EnumValue: Colors.red [value=1]>
-    >>> getattr(Colors, 'green')
-    <EnumValue: Colors.green [value=2]>
+    >>> 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
+    >>> Color.red is Color.blue
+    False
+    >>> Color.red is not Color.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
+    >>> 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 "<stdin>", line 1, in <module>
+    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 @@
     >>> class SpecialId(Enum):
     ...   selector = '$IM($N)'
     ...   adaptor = '~$IM'
-    ...
+    ... 
     >>> SpecialId.selector
-    <EnumValue: SpecialId.selector [value=$IM($N)]>
+    SpecialId.selector [value='$IM($N)']
     >>> SpecialId.selector.value
     '$IM($N)'
     >>> a = SpecialId.adaptor
@@ -342,8 +264,6 @@
     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 @@
 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 @@
     ...   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 @@
 
 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
+    >>> Animal = Enum('Animal', 'ant bee cat dog')
+    >>> Animal
+    <Enum '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 @@
     ...   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.
 
+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 @@
 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 @@
 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 @@
 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 @@
 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:

-- 
Repository URL: http://hg.python.org/peps


More information about the Python-checkins mailing list