Updated PEP 435:
* split the main section to some subsections, to make it more readable * described non-int values * removed mentions of implicit int-ness of Enum, which is no longer supported (unless in IntEnum) * described the new convenience API * misc. cleanups
This commit is contained in:
parent
cb3c841bab
commit
44b674e3a4
344
pep-0435.txt
344
pep-0435.txt
|
@ -20,11 +20,9 @@ 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 integer
|
||||
values. Within an enumeration, the values can be compared by identity, and the
|
||||
enumeration itself can be iterated over. Enumeration items can be converted to
|
||||
and from their integer equivalents, supporting use cases such as storing
|
||||
enumeration values in a database.
|
||||
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
|
||||
|
@ -109,17 +107,6 @@ with assignment to their integer values::
|
|||
... green = 2
|
||||
... blue = 3
|
||||
|
||||
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
|
||||
|
||||
Enumeration values have nice, human readable string representations::
|
||||
|
||||
>>> print(Colors.red)
|
||||
|
@ -161,6 +148,107 @@ The str and repr of the enumeration class also provides useful information::
|
|||
>>> print(repr(Colors))
|
||||
<Colors {red: 1, green: 2, blue: 3}>
|
||||
|
||||
The ``Enum`` class supports iteration. The returned order is not guaranteed
|
||||
(unless you use `IntEnum`_)::
|
||||
|
||||
>>> [v.name for v in MoreColors]
|
||||
['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'}
|
||||
|
||||
You can convert back to the enumeration value by indexing into the
|
||||
``Enum`` subclass, passing in the integer value for the item you want::
|
||||
|
||||
>>> Colors[1]
|
||||
<EnumValue: Colors.red [int=1]>
|
||||
>>> Colors[2]
|
||||
<EnumValue: Colors.green [int=2]>
|
||||
>>> Colors[3]
|
||||
<EnumValue: Colors.blue [int=3]>
|
||||
>>> Colors[1] is Colors.red
|
||||
True
|
||||
|
||||
The string name of the enumeration value is also accepted::
|
||||
|
||||
>>> Colors['red']
|
||||
<EnumValue: Colors.red [int=1]>
|
||||
>>> Colors['blue'] is Colors.blue
|
||||
True
|
||||
|
||||
You get exceptions though, if you try to use invalid arguments::
|
||||
|
||||
>>> Colors['magenta']
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: magenta
|
||||
>>> Colors[99]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 99
|
||||
|
||||
|
||||
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):
|
||||
|
@ -195,112 +283,7 @@ These enumeration values are not equal, nor do they hash equally::
|
|||
>>> len(set((Colors.red, OtherColors.red)))
|
||||
2
|
||||
|
||||
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
|
||||
|
||||
Enumeration values do not support ordered comparisons::
|
||||
|
||||
>>> Colors.red < Colors.blue
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
NotImplementedError
|
||||
>>> Colors.red < 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
NotImplementedError
|
||||
>>> Colors.red <= 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
NotImplementedError
|
||||
>>> Colors.blue > 2
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
NotImplementedError
|
||||
>>> Colors.blue >= 2
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
NotImplementedError
|
||||
|
||||
While equality comparisons are allowed, 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
|
||||
|
||||
If you really want the integer equivalent values, you can convert enumeration
|
||||
values explicitly using the ``int()`` built-in. This is quite convenient for
|
||||
storing enums in a database, as well as for interoperability with C extensions
|
||||
that expect integers::
|
||||
|
||||
>>> int(Colors.red)
|
||||
1
|
||||
>>> int(Colors.green)
|
||||
2
|
||||
>>> int(Colors.blue)
|
||||
3
|
||||
|
||||
You can also convert back to the enumeration value by indexing into the Enum
|
||||
subclass, passing in the integer value for the item you want::
|
||||
|
||||
>>> Colors[1]
|
||||
<EnumValue: Colors.red [int=1]>
|
||||
>>> Colors[2]
|
||||
<EnumValue: Colors.green [int=2]>
|
||||
>>> Colors[3]
|
||||
<EnumValue: Colors.blue [int=3]>
|
||||
>>> Colors[1] is Colors.red
|
||||
True
|
||||
|
||||
The string name of the enumeration value is also accepted::
|
||||
|
||||
>>> Colors['red']
|
||||
<EnumValue: Colors.red [int=1]>
|
||||
>>> Colors['blue'] is Colors.blue
|
||||
True
|
||||
|
||||
You get exceptions though, if you try to use invalid arguments::
|
||||
|
||||
>>> Colors['magenta']
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: magenta
|
||||
>>> Colors[99]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 99
|
||||
|
||||
The integer equivalent values serve another purpose. You may not define two
|
||||
enumeration values with the same integer value::
|
||||
You may not define two enumeration values with the same integer value::
|
||||
|
||||
>>> class Bad(Enum):
|
||||
... cartman = 1
|
||||
|
@ -321,23 +304,42 @@ You also may not duplicate values in derived enumerations::
|
|||
...
|
||||
TypeError: Multiple enum values: 2
|
||||
|
||||
The Enum class support iteration. Enumeration values are returned in the
|
||||
sorted order of their integer equivalent values::
|
||||
|
||||
>>> [v.name for v in MoreColors]
|
||||
['red', 'green', 'blue', 'pink', 'cyan']
|
||||
>>> [int(v) for v in MoreColors]
|
||||
[1, 2, 3, 4, 5]
|
||||
Enumeration values
|
||||
------------------
|
||||
|
||||
Enumeration values are hashable, so they can be used in dictionaries and sets::
|
||||
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.
|
||||
|
||||
>>> apples = {}
|
||||
>>> apples[Colors.red] = 'red delicious'
|
||||
>>> apples[Colors.green] = 'granny smith'
|
||||
>>> for color in sorted(apples, key=int):
|
||||
... print(color.name, '->', apples[color])
|
||||
red -> red delicious
|
||||
green -> granny smith
|
||||
|
||||
IntEnum
|
||||
-------
|
||||
|
@ -377,6 +379,15 @@ However they still can't be compared to ``Enum``::
|
|||
>>> Shape.circle == Colors.red
|
||||
False
|
||||
|
||||
``IntEnum`` behaves like an integer 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
|
||||
|
@ -384,6 +395,7 @@ 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
|
||||
--------
|
||||
|
||||
|
@ -394,36 +406,42 @@ Enumerations created with the class syntax can also be pickled and unpickled::
|
|||
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
|
||||
True
|
||||
|
||||
|
||||
Convenience API
|
||||
---------------
|
||||
|
||||
TODO: update to the new convenience API
|
||||
The ``Enum`` class is callable, providing the following convenience API::
|
||||
|
||||
You can also create enumerations using the convenience function ``make()``,
|
||||
which takes an iterable object or dictionary to provide the item names and
|
||||
values. ``make()`` is a module-level function.
|
||||
|
||||
The first argument to ``make()`` is the name of the enumeration, and it returns
|
||||
the so-named `Enum` subclass. The second argument is a *source* which can be
|
||||
either an iterable or a dictionary. In the most basic usage, *source* returns
|
||||
a sequence of strings which name the enumeration items. In this case, the
|
||||
values are automatically assigned starting from 1::
|
||||
|
||||
>>> import enum
|
||||
>>> enum.make('Animals', ('ant', 'bee', 'cat', 'dog'))
|
||||
>>> 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 items in source can also be 2-tuples, where the first item is the
|
||||
enumeration value name and the second is the integer value to assign to the
|
||||
value. If 2-tuples are used, all items must be 2-tuples::
|
||||
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::
|
||||
|
||||
>>> def enumiter():
|
||||
... start = 1
|
||||
... while True:
|
||||
... yield start
|
||||
... start <<= 1
|
||||
>>> enum.make('Flags', zip(list('abcdefg'), enumiter()))
|
||||
<Flags {a: 1, b: 2, c: 4, d: 8, e: 16, f: 32, g: 64}>
|
||||
>>> 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}>
|
||||
|
||||
|
||||
Proposed variations
|
||||
|
@ -432,6 +450,7 @@ 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
|
||||
--------------------------------------
|
||||
|
||||
|
@ -450,6 +469,7 @@ 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
|
||||
-------------------------------------------------------
|
||||
|
||||
|
@ -571,12 +591,6 @@ Todo
|
|||
====
|
||||
|
||||
* Mark PEP 354 "superseded by" this one, if accepted
|
||||
* New package name within stdlib - enum? (top-level)
|
||||
* For make, can we add an API like namedtuple's?
|
||||
make('Animals, 'ant bee cat dog')
|
||||
I.e. when make sees a string argument it splits it, making it similar to a
|
||||
tuple but with far less manual quote typing. OTOH, it just saves a ".split"
|
||||
so may not be worth the effort ?
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
|
|
Loading…
Reference in New Issue