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"
|
Warsaw into the standard library. Much of this PEP is based on the "using"
|
||||||
[1]_ document from the documentation of ``flufl.enum``.
|
[1]_ document from the documentation of ``flufl.enum``.
|
||||||
|
|
||||||
An enumeration is a set of symbolic names bound to unique, constant integer
|
An enumeration is a set of symbolic names bound to unique, constant values.
|
||||||
values. Within an enumeration, the values can be compared by identity, and the
|
Within an enumeration, the values can be compared by identity, and the
|
||||||
enumeration itself can be iterated over. Enumeration items can be converted to
|
enumeration itself can be iterated over.
|
||||||
and from their integer equivalents, supporting use cases such as storing
|
|
||||||
enumeration values in a database.
|
|
||||||
|
|
||||||
|
|
||||||
Decision
|
Decision
|
||||||
|
@ -109,17 +107,6 @@ with assignment to their integer values::
|
||||||
... green = 2
|
... green = 2
|
||||||
... blue = 3
|
... 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::
|
Enumeration values have nice, human readable string representations::
|
||||||
|
|
||||||
>>> print(Colors.red)
|
>>> print(Colors.red)
|
||||||
|
@ -161,6 +148,107 @@ The str and repr of the enumeration class also provides useful information::
|
||||||
>>> print(repr(Colors))
|
>>> print(repr(Colors))
|
||||||
<Colors {red: 1, green: 2, blue: 3}>
|
<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::
|
You can extend previously defined Enums by subclassing::
|
||||||
|
|
||||||
>>> class MoreColors(Colors):
|
>>> class MoreColors(Colors):
|
||||||
|
@ -195,112 +283,7 @@ These enumeration values are not equal, nor do they hash equally::
|
||||||
>>> len(set((Colors.red, OtherColors.red)))
|
>>> len(set((Colors.red, OtherColors.red)))
|
||||||
2
|
2
|
||||||
|
|
||||||
Ordered comparisons between enumeration values are *not* supported. Enums are
|
You may not define two enumeration values with the same integer value::
|
||||||
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::
|
|
||||||
|
|
||||||
>>> class Bad(Enum):
|
>>> class Bad(Enum):
|
||||||
... cartman = 1
|
... cartman = 1
|
||||||
|
@ -321,23 +304,42 @@ You also may not duplicate values in derived enumerations::
|
||||||
...
|
...
|
||||||
TypeError: Multiple enum values: 2
|
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]
|
Enumeration values
|
||||||
['red', 'green', 'blue', 'pink', 'cyan']
|
------------------
|
||||||
>>> [int(v) for v in MoreColors]
|
|
||||||
[1, 2, 3, 4, 5]
|
|
||||||
|
|
||||||
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
|
IntEnum
|
||||||
-------
|
-------
|
||||||
|
@ -377,6 +379,15 @@ However they still can't be compared to ``Enum``::
|
||||||
>>> Shape.circle == Colors.red
|
>>> Shape.circle == Colors.red
|
||||||
False
|
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
|
For the vast majority of code, ``Enum`` is strongly recommended. Since
|
||||||
``IntEnum`` breaks some semantic promises of an enumeration (by being comparable
|
``IntEnum`` breaks some semantic promises of an enumeration (by being comparable
|
||||||
to integers, and thus by transitivity to other unrelated enumerations), it
|
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
|
when integer constants are replaced with enumerations and backwards
|
||||||
compatibility is required with code that still expects integers.
|
compatibility is required with code that still expects integers.
|
||||||
|
|
||||||
|
|
||||||
Pickling
|
Pickling
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -394,36 +406,42 @@ Enumerations created with the class syntax can also be pickled and unpickled::
|
||||||
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
|
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
|
||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
Convenience API
|
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()``,
|
>>> Animals = Enum('Animals', 'ant bee cat dog')
|
||||||
which takes an iterable object or dictionary to provide the item names and
|
>>> Animals
|
||||||
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 {ant: 1, bee: 2, cat: 3, dog: 4}>
|
<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
|
The semantics of this API resemble ``namedtuple``. The first argument of
|
||||||
enumeration value name and the second is the integer value to assign to the
|
the call to ``Enum`` is the name of the enumeration. The second argument is
|
||||||
value. If 2-tuples are used, all items must be 2-tuples::
|
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():
|
>>> class Animals(Enum):
|
||||||
... start = 1
|
... ant = 1
|
||||||
... while True:
|
... bee = 2
|
||||||
... yield start
|
... cat = 3
|
||||||
... start <<= 1
|
... dog = 4
|
||||||
>>> enum.make('Flags', zip(list('abcdefg'), enumiter()))
|
|
||||||
<Flags {a: 1, b: 2, c: 4, d: 8, e: 16, f: 32, g: 64}>
|
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
|
Proposed variations
|
||||||
|
@ -432,6 +450,7 @@ 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.
|
||||||
|
|
||||||
|
|
||||||
Not having to specify values for enums
|
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
|
definition of such enums baffling when first seen. Besides, explicit is
|
||||||
better than implicit.
|
better than implicit.
|
||||||
|
|
||||||
|
|
||||||
Using special names or forms to auto-assign enum values
|
Using special names or forms to auto-assign enum values
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
@ -571,12 +591,6 @@ Todo
|
||||||
====
|
====
|
||||||
|
|
||||||
* Mark PEP 354 "superseded by" this one, if accepted
|
* 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:
|
Local Variables:
|
||||||
|
|
Loading…
Reference in New Issue