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
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::
>>> from enum import Enum
@ -118,7 +118,7 @@ Enumeration members have human readable string representations::
...while their ``repr`` has more information::
>>> print(repr(Color.red))
Color.red [value=1]
<Color.red: 1>
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'>
>>> isinstance(Color.green, Color)
True
>>>
>>>
Enums also have a property that contains just their item name::
@ -140,10 +140,10 @@ Enumerations support iteration, in definition order::
... chocolate = 4
... cookies = 9
... mint = 3
...
...
>>> for shake in Shake:
... print(shake)
...
...
Shake.vanilla
Shake.chocolate
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.green] = 'granny smith'
>>> 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
------------------------------------------
@ -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::
>>> Color(1)
Color.red [value=1]
<Color.red: 1>
>>> Color(3)
Color.blue [value=3]
<Colro.blue: 3>
If you want to access enum members by *name*, ``Enum`` works as expected with
``getattr``::
If you want to access enum members by *name*, use item access::
>>> 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
-----------------------------------
@ -185,26 +186,39 @@ 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::
However, two enum members are allowed to have the same value. Given two members
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):
... square = 2
... diamond = 1
... circle = 3
... alias_for_square = 2
...
...
>>> Shape.square
Shape.square [value=2]
<Shape.square: 2>
>>> Shape.alias_for_square
Shape.square [value=2]
<Shape.square: 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
-----------
@ -240,38 +254,15 @@ below)::
>>> Color.blue == 2
False
Allowed members and attributs of enumerations
---------------------------------------------
Allowed members and attributes 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
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
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
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 can have arbitrary values.
Enumerations are Python classes, and can have methods and special methods as
usual. If we have this enumeration::
@ -295,7 +286,7 @@ usual. If we have this enumeration::
Then::
>>> Mood.favorite_mood()
Mood.happy [value=3]
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> 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
*__dunder__* names and descriptors; methods are descriptors too.
Restricted subclassing of enumerations
--------------------------------------
@ -313,19 +305,19 @@ any members. So this is forbidden::
>>> class MoreColor(Color):
... pink = 17
...
TypeError: Cannot subclass enumerations
...
TypeError: Cannot extend 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
@ -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
``IntEnum``.
IntEnum
-------
@ -388,31 +381,41 @@ replaced with enumerations and backwards compatibility is required
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
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True
class IntEnum(int, Enum):
pass
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'>
>>> Animal.ant
Animal.ant [value=1]
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> 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 call to ``Enum`` is the name of the enumeration. The second argument is
@ -429,12 +432,29 @@ equivalent to::
... cat = 3
... 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
===================
Some variations were proposed during the discussions in the mailing list.
Here's some of the more popular ones.
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
violated in ``flufl.enum`` with such a scheme).
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
vs. implicit applies here as well.
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
can be seen in test protocols written with the Tulip library as well.
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.
Ben Finney is the author of the earlier enumeration PEP 354.
References
==========