Update PEP 435 with latest decisions and add some clarifications:

1. getitem syntax come-back
2. Define iteration in presence of aliases
3. __aliases__
4. s/Convenience/Functional/ API
5. Don't say enums created with the functional API can't be pickled, but
   provide the usual precaution from the doc of pickle
This commit is contained in:
Eli Bendersky 2013-05-04 06:28:32 -07:00
parent fa6769dc34
commit 440ad4c854
1 changed files with 90 additions and 66 deletions

View File

@ -96,7 +96,7 @@ 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 `Functional API`_.
To define an enumeration, subclass ``Enum`` as follows:: To define an enumeration, subclass ``Enum`` as follows::
>>> from enum import Enum >>> from enum import Enum
@ -118,7 +118,7 @@ Enumeration members have human readable string representations::
...while their ``repr`` has more information:: ...while their ``repr`` has more information::
>>> print(repr(Color.red)) >>> print(repr(Color.red))
Color.red [value=1] <Color.red: 1>
The *type* of an enumeration member is the enumeration it belongs to:: The *type* of an enumeration member is the enumeration it belongs to::
@ -126,7 +126,7 @@ The *type* of an enumeration member is the enumeration it belongs to::
<Enum 'Color'> <Enum 'Color'>
>>> isinstance(Color.green, Color) >>> isinstance(Color.green, Color)
True True
>>> >>>
Enums also have a property that contains just their item name:: Enums also have a property that contains just their item name::
@ -140,10 +140,10 @@ Enumerations support iteration, in definition order::
... chocolate = 4 ... chocolate = 4
... cookies = 9 ... cookies = 9
... mint = 3 ... mint = 3
... ...
>>> for shake in Shake: >>> for shake in Shake:
... print(shake) ... print(shake)
... ...
Shake.vanilla Shake.vanilla
Shake.chocolate Shake.chocolate
Shake.cookies Shake.cookies
@ -155,7 +155,8 @@ Enumeration members are hashable, so they can be used in dictionaries and sets::
>>> apples[Color.red] = 'red delicious' >>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith' >>> apples[Color.green] = 'granny smith'
>>> apples >>> apples
{Color.red [value=1]: 'red delicious', Color.green [value=2]: 'granny smith'} {<Color.red: 1>: 'red delicious', <Color.green: 2>: 'granny smith'}
Programmatic access to enumeration members Programmatic access to enumeration members
------------------------------------------ ------------------------------------------
@ -165,17 +166,17 @@ situations where ``Color.red`` won't do because the exact color is not known
at program-writing time). ``Enum`` allows such access:: at program-writing time). ``Enum`` allows such access::
>>> Color(1) >>> Color(1)
Color.red [value=1] <Color.red: 1>
>>> Color(3) >>> Color(3)
Color.blue [value=3] <Colro.blue: 3>
If you want to access enum members by *name*, ``Enum`` works as expected with If you want to access enum members by *name*, use item access::
``getattr``::
>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>
>>> getattr(Color, 'red')
Color.red [value=1]
>>> getattr(Color, 'green')
Color.green [value=2]
Duplicating enum members and values Duplicating enum members and values
----------------------------------- -----------------------------------
@ -185,26 +186,39 @@ Having two enum members with the same name is invalid::
>>> class Shape(Enum): >>> class Shape(Enum):
... square = 2 ... square = 2
... square = 3 ... square = 3
... ...
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: Attempted to reuse key: square TypeError: Attempted to reuse key: square
However, two enum members are allowed to have the same value. By-value lookup However, two enum members are allowed to have the same value. Given two members
will then access the *earliest defined* member:: A and B with the same value (and A defined first), B is an alias to A. By-value
lookup of the value of A and B will return A.
>>> class Shape(Enum): >>> class Shape(Enum):
... square = 2 ... square = 2
... diamond = 1 ... diamond = 1
... circle = 3 ... circle = 3
... alias_for_square = 2 ... alias_for_square = 2
... ...
>>> Shape.square >>> Shape.square
Shape.square [value=2] <Shape.square: 2>
>>> Shape.alias_for_square >>> Shape.alias_for_square
Shape.square [value=2] <Shape.square: 2>
>>> Shape(2) >>> Shape(2)
Shape.square [value=2] <Shape.square: 2>
Iterating over the members of an enum does not provide the aliases::
>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]
If access to aliases is required for some reason, use the special attribute
``__aliases__``::
>>> Shape.__aliases__
['alias_for_square']
Comparisons Comparisons
----------- -----------
@ -240,38 +254,15 @@ below)::
>>> Color.blue == 2 >>> Color.blue == 2
False False
Allowed members and attributs of enumerations
--------------------------------------------- Allowed members and attributes of enumerations
----------------------------------------------
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 `Functional API`_), but not
strictly enforced. In the vast majority of use-cases, one doesn't care what 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, the actual value of an enumeration is. But if the value *is* important,
enumerations can have arbitrary values. The following example uses strings:: enumerations can have arbitrary values.
>>> class SpecialId(Enum):
... selector = '$IM($N)'
... adaptor = '~$IM'
...
>>> SpecialId.selector
SpecialId.selector [value='$IM($N)']
>>> SpecialId.selector.value
'$IM($N)'
>>> a = SpecialId.adaptor
>>> a == '~$IM'
False
>>> a == SpecialId.adaptor
True
>>> print(a)
SpecialId.adaptor
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.
Enumerations are Python classes, and can have methods and special methods as Enumerations are Python classes, and can have methods and special methods as
usual. If we have this enumeration:: usual. If we have this enumeration::
@ -295,7 +286,7 @@ usual. If we have this enumeration::
Then:: Then::
>>> Mood.favorite_mood() >>> Mood.favorite_mood()
Mood.happy [value=3] <Mood.happy: 3>
>>> Mood.happy.describe() >>> Mood.happy.describe()
('happy', 3) ('happy', 3)
>>> str(Mood.funky) >>> str(Mood.funky)
@ -305,6 +296,7 @@ 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 enumeration will become members of this enumeration, with the exception of
*__dunder__* names and descriptors; methods are descriptors too. *__dunder__* names and descriptors; methods are descriptors too.
Restricted subclassing of enumerations Restricted subclassing of enumerations
-------------------------------------- --------------------------------------
@ -313,19 +305,19 @@ any members. So this is forbidden::
>>> class MoreColor(Color): >>> class MoreColor(Color):
... pink = 17 ... pink = 17
... ...
TypeError: Cannot subclass enumerations TypeError: Cannot extend enumerations
But this is allowed:: But this is allowed::
>>> class Foo(Enum): >>> class Foo(Enum):
... def some_behavior(self): ... def some_behavior(self):
... pass ... pass
... ...
>>> class Bar(Foo): >>> class Bar(Foo):
... happy = 1 ... happy = 1
... sad = 2 ... sad = 2
... ...
The rationale for this decision was given by Guido in [6]_. Allowing to 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 subclass enums that define members would lead to a violation of some
@ -334,6 +326,7 @@ makes sense to allow sharing some common behavior between a group of
enumerations, and subclassing empty enumerations is also used to implement enumerations, and subclassing empty enumerations is also used to implement
``IntEnum``. ``IntEnum``.
IntEnum IntEnum
------- -------
@ -388,31 +381,41 @@ replaced with enumerations and backwards compatibility is required
with code that still expects integers. with code that still expects integers.
Pickling Other derived enumerations
-------- --------------------------
Enumerations created with the class syntax can also be pickled and unpickled:: ``IntEnum`` will be part of the ``enum`` module. However, it would be very
simple to implement independently::
>>> from enum.tests.fruit import Fruit class IntEnum(int, Enum):
>>> from pickle import dumps, loads pass
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True This demonstrates how similar derived enumerations can be defined, for example
a ``StrEnum`` that mixes in ``str`` instead of ``int``.
Some rules:
1. When subclassing Enum, mixing types must appear before Enum itself in the
sequence of bases.
2. While Enum can have members of any type, once you mix in an additional
type, all the members must have values of that type, e.g. ``int`` above.
This restriction does not apply to behavior-only mixins.
Convenience API Functional API
--------------- --------------
The ``Enum`` class is callable, providing the following convenience API:: The ``Enum`` class is callable, providing the following functional API::
>>> Animal = Enum('Animal', 'ant bee cat dog') >>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal >>> Animal
<Enum 'Animal'> <Enum 'Animal'>
>>> Animal.ant >>> Animal.ant
Animal.ant [value=1] <Animal.ant: 1>
>>> Animal.ant.value >>> Animal.ant.value
1 1
>>> list(Animal) >>> list(Animal)
[Animal.ant [value=1], Animal.bee [value=2], Animal.cat [value=3], Animal.dog [value=4]] [<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 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
@ -429,12 +432,29 @@ equivalent to::
... cat = 3 ... cat = 3
... dog = 4 ... dog = 4
Pickling
--------
Enumerations be pickled and unpickled::
>>> from enum.tests.fruit import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True
The usual restrictions for pickling apply: picklable enums must be defined in
the top level of a module, to be importable from that module when unpickling
occurs.
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
---------- ----------
@ -446,6 +466,7 @@ also explicitly permits subclassing enumerations for extending them with
more members (due to the member/enum separation, the type invariants are not more members (due to the member/enum separation, the type invariants are not
violated in ``flufl.enum`` with such a scheme). violated in ``flufl.enum`` with such a scheme).
Not having to specify values for enums Not having to specify values for enums
-------------------------------------- --------------------------------------
@ -495,6 +516,7 @@ 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
================================= =================================
@ -528,6 +550,7 @@ 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.
Acknowledgments Acknowledgments
=============== ===============
@ -535,6 +558,7 @@ 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. 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. Ben Finney is the author of the earlier enumeration PEP 354.
References References
========== ==========