diff --git a/pep-0435.txt b/pep-0435.txt index 99148a6e1..312b486df 100644 --- a/pep-0435.txt +++ b/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] + 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:: >>> 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'} + {: 'red delicious', : '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(3) - Color.blue [value=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['green'] + - >>> 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.alias_for_square - Shape.square [value=2] + >>> Shape(2) - Shape.square [value=2] + + +Iterating over the members of an enum does not provide the aliases:: + + >>> list(Shape) + [, , ] + +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.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 >>> Animal.ant - Animal.ant [value=1] + >>> Animal.ant.value 1 >>> list(Animal) - [Animal.ant [value=1], Animal.bee [value=2], Animal.cat [value=3], Animal.dog [value=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 ==========