[Python-Dev] PEP 435 -- Adding an Enum type to the Python standard library

Guido van Rossum guido at python.org
Sat Apr 27 18:01:54 CEST 2013


I've always found the nested class solution confusing when I had to
use it in Django. It is a technical solution to a technical problem,
but the resulting code is not very readable unless you have seen it a
lot; it is a new, exceptional pattern.

And as for using 'in' instead of 'isinstance' to check whether a value
belongs to a given enum class, that, too, is a deviation from normal
practice, which will require special cases in frameworks provide e.g.
type checking. (As I mentioned before, any framework using argument
annotations to indicate the expected type(s) for arguments would have
to special-case the checks for enums.)

Please let's try harder to come up with a way to separate enum value
definitions from method definitions that works as expected in most
common cases. I am willing to concede that it's hard to support things
like class variables; maybe __private variables can be used for these;
or the code can just use  globals instead of class variables to hold
per-class state. And __init__/__new__ probably shouldn't be
overridden. But instance methods, class methods, static methods,
properties, and other decorated methods should all work, as should
special methods like __add__ or __getitem__.

Hm... A lightbulb just went off. Objects representing both undecorated
and decorated methods have a __get__() method, courtesy of the
descriptor protocol. Maybe checking for that will work? It feels
Pythonic to me: it uses a corner of the language that most people
don't even know exists (*), but it produces the desired effect in
almost all cases that matter, the pattern is simple to describe and
easy to use without thinking about it, and for experts the rules are
completely clear, uncomplicated, and free of heuristics, so it is
possible to reason about corner cases.

(*) Proof: even I didn't think of it until just now. :-)


On Sat, Apr 27, 2013 at 6:17 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> On Sat, Apr 27, 2013 at 7:41 AM, Guido van Rossum <guido at python.org> wrote:
>> On Fri, Apr 26, 2013 at 11:17 AM, Eli Bendersky <eliben at gmail.com> wrote:
>>> In contrast, personally I feel the current proposal in PEP 435 has an appeal
>>> from the POV of simplicity. It really is a very nice separation of concerns
>>> between enum values and Enum as a container of such values. It even allows
>>> significant customization (IntEnum, etc) which is pretty simple to grok. It
>>> would be a shame to lose these for the sake of making Python a bit more like
>>> Java.
>>
>> But it's not so much the "like Java" that matters to me. It's the
>> realization that for the user who wants to define an enum type with
>> some extra functionality, having a single class and putting the
>> methods and the items in the same class is the simplest way to do it.
>> The Java reference is just to point out that we're not exactly
>> breaking new ground here.
>
> A common idiom in some other use cases (like ORMs) is to allow an
> inner class to customise behaviour beyond what the basic class syntax
> allows. It seems like that may be a good fit here, as a couple of
> simple changes should greatly simplify the process of tweaking the
> behaviour of the enum items, without adding more complexity to the
> implementation:
>
> 1. Change the name of "__value_factory__" to something more concise
> like "__enumitem__".
> 2. Make EnumItem and IntEnumItem public classes
>
> (Barry has already noted that the current naming the associated
> classes in flufl.enum and PEP 435 isn't quite right, since the
> intended terminology is enum for the class, enum item for the labelled
> values, and value for the raw unlabelled objects, but the class names
> are currently EnumValue and IntEnumValue).
>
> Then, if you want to add custom behaviour to your enum, you would be
> able to do use a nested class relatively cleanly:
>
>     class MyEnum(Enum):
>         itemA = 1
>         itemB = 2
>
>         class __enumitem__(EnumItem):
>             def __init__(self, enum, value, name):
>                 if not name.startswith("item"):
>                     raise ValueError("Item name {} doesn't start with
> 'item'".format(name))
>                 super().__init__(enum, value, name)
>             @property
>             def letter(self):
>                 return self.name[4:]
>
>     class MyExtendedEnum(MyEnum):
>         # The custom __enumitem__ is inherited along with the original
> attributes
>         itemC = 3
>         itemD = 4
>
> Furthermore, rather than tweaking isinstance checks, it may make more
> sense to support containment testing for enums, such that you can
> write things like:
>
>     assert MyEnum.itemA in MyEnum
>     assert 1 not in MyEnum
>
> Cheers,
> Nick.
>
> --
> Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



-- 
--Guido van Rossum (python.org/~guido)


More information about the Python-Dev mailing list