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:
parent
fa6769dc34
commit
440ad4c854
156
pep-0435.txt
156
pep-0435.txt
|
@ -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
|
||||
==========
|
||||
|
||||
|
|
Loading…
Reference in New Issue