[Python-ideas] Yet another enum proposal :)

Alex Stewart foogod at gmail.com
Fri Feb 22 20:25:08 CET 2013


Ok, so at the risk of muddying the waters even more, I've put together yet 
another possible way to do enums, and would be interested to hear comments..

Based on the list of required/desired/undesired properties laid out in the 
enum PEP thread, I believe a lot of the back and forth to date has been due 
to the fact that all of the proposed implementations fall short of 
fulfilling at least some of the desired properties for a general-purpose 
enum implementation in different ways.  I've put together something based 
on ideas from Tim's, Barry's, other things thrown out in discussion, and a 
few of my own which I think comes closer to ticking off most of the boxes.

(The code and some examples are available at 
https://github.com/foogod/pyenum)

Enums/groups are defined by creating subclasses of Enum, similarly to 
Barry's implementation.  The distinction is that the base "Enum" class does 
not have any associated values (they're just singleton objects with names). 
 At a basic level, the values themselves are defined like so:

class Color (Enum):
    RED = __
    GREEN = __
    BLUE = __

As has been (quite correctly) pointed out before, the single-underscore (_) 
has a well-established meaning in most circles related to gettext/etc, so 
for this application I picked the next best thing, the double-underscore 
(__) instead.  I think this is reasonably mnemonic as a "fill in the blank" 
placeholder, and also not unduly verbose.  One advantage of using __ over, 
say, ellipsis (...) is that since it involves a name resolution, we can add 
(just a little!) magic to generate distinct __ objects on each reference, 
so, for example, the following actually works as the user probably expects 
it to:

class Color (Enum):
    RED = CRIMSON = __
    BLUE = __

(RED and CRIMSON actually become aliases referring to the same enum value, 
but BLUE is a different enum value)

One other rather notable advantage to __ is that we can define a special 
multiplication behavior for it which allows us to make the syntax much more 
compact:

class Color (Enum):
    RED, GREEN, BLUE, ORANGE, VIOLET, BEIGE, ULTRAVIOLET, ULTRABEIGE = __ * 
8

(as an aside, I have an idea about how we might be able to get rid of the 
"* 8" altogether, but it requires a different (I think generally useful) 
change to the language which I will propose separately)

Each enum value is actually an instance of its defining class, so you can 
determine what type of enum something is with simple inheritance checks:

>>> isinstance(Color.RED, Color)
True

For enums which need to have int values, we can use IntEnum instead of Enum:

class Errno (IntEnum):
    EPERM = 1
    ENOENT = 2
    ESRCH = 3
    EINTR = 4

(Technically, IntEnum is just a special case of the more generic TypeEnum 
class:

class IntEnum (TypeEnum, basetype=int):
    pass

..which means that theoretically you could define enums based on (almost) 
any base type (examples.py has an example using floats))

Anyway, as expected, enums have reasonable strs/reprs, and can be easily 
translated to/from base values using traditional "casting" syntax:

>>> Errno.EPERM
<__main__.Errno.EPERM (1)>
>>> str(Errno.EPERM)
'EPERM'
>>> int(Errno.EPERM)
1
>>> Errno(1)
<__main__.Errno.EPERM (1)>

You can also lookup enums by name using index notation:

>>> Errno['EPERM']
<__main__.Errno.EPERM (1)>

It's actually worth noting here that TypeEnums are actually subclasses of 
their basetype, so IntEnums are also ints, and can be used as drop-in 
replacements for any existing code which is expecting an int argument. 
 They can also be compared directly as if they were ints:

if exc.errno == Errno.EPERM:
   do_something()

TypeEnums enforce uniqueness:

>>> class Foo (IntEnum):
...     A = 1
...     B = 1
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
ValueError: Duplicate enum value: 1

However, you can define "aliases" for enums, if you want to:

>>> class Foo (IntEnum):
...     A = 1
...     B = A
... 
>>> Foo.A
<__main__.Foo.A (1)>
>>> Foo.B
<__main__.Foo.A (1)>

Of course, pure (valueless) Enum instances are logical singletons (i.e. 
they only compare equal to themselves), so there's no potential for 
duplication there.

Finally, a special feature of __ is that it can also be called with 
parameters to create enum values with docstrings or other metadata:

>>> class Errno (IntEnum):
...     EPERM = __(1, doc="Permission denied")
... 
>>> Errno.EPERM.__doc__
'Permission denied'
>>> class Color (Enum):
...     RED = __(doc="The color red", rgb=(1., 0., 0.))
... 
>>> Color.RED.__doc__
'The color red'
>>> Color.RED.rgb
(1.0, 0.0, 0.0)

A few other notable properties:

   - Enum values are hashable, so they can be used as dict keys, etc.
   - Though enum values will compare equal to their associated basetype 
   values (Errno.EPERM == 1), enum values from different classes never compare 
   equal to each other (Foo.A != Errno.EPERM).  This prevents enum-aware code 
   from accidentally mistaking one enum for a completely different enum (with 
   a completely different meaning) simply because they map to the same int 
   value.
   - Using the class constructor does not create new enums, it just returns 
   the existing singleton enum associated with the passed value (Errno(1) 
   returns Errno.EPERM, not a new Errno).  If there is no predefined enum 
   matching that value, a ValueError is raised, thus using the constructor is 
   basically a "cast" operation, and only works within the supported range of 
   values.  (New enum values can be created using the .new() method on the 
   class, however, if desired)

So... thoughts?

--Alex

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20130222/05725d90/attachment.html>


More information about the Python-ideas mailing list