From 44b674e3a4ac1791f24483cd9f18b1312b115b10 Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Thu, 4 Apr 2013 06:52:14 -0700 Subject: [PATCH] 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 --- pep-0435.txt | 344 +++++++++++++++++++++++++++------------------------ 1 file changed, 179 insertions(+), 165 deletions(-) diff --git a/pep-0435.txt b/pep-0435.txt index 7f810312c..239d33101 100644 --- a/pep-0435.txt +++ b/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)) +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 + {: 'granny smith', : '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] + + >>> Colors[2] + + >>> Colors[3] + + >>> Colors[1] is Colors.red + True + +The string name of the enumeration value is also accepted:: + + >>> Colors['red'] + + >>> 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] - - >>> Colors[2] - - >>> Colors[3] - - >>> Colors[1] is Colors.red - True - -The string name of the enumeration value is also accepted:: - - >>> Colors['red'] - - >>> 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 + + >>> 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 + + >>> 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())) - + >>> class Animals(Enum): + ... ant = 1 + ... bee = 2 + ... cat = 3 + ... dog = 4 + +Examples of alternative name/value specifications:: + + >>> Enum('Animals', ['ant', 'bee', 'cat', 'dog']) + + >>> Enum('Animals', (('ant', 'one'), ('bee', 'two'), ('cat', 'three'), ('dog', 'four'))) + 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: