[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