A simpler Enum for Python 3.
Install simple-enum
from
PyPI:
easy_install simple-enum
An Enum is an ordered, immutable dictionary. You only need to provide the names:
>>> from simplenum import Enum
>>> class Colour(Enum):
... red
... green
... blue
...
>>> list(Colour)
['red', 'green', 'blue']
>>> 'red' in Colour
True
By default, the dictionary values are the names themselves:
>>> Colour['red']
'red'
but you can change that using values
:
>>> from simplenum import from_one
>>> class Weekday(Enum, values=from_one):
... monday, tuesday, wednesday, thursday, friday, saturday, sunday
...
>>> Weekday['monday']
1
An Enum is also an ordered, immutable set of named tuples:
>>> class Colour(Enum):
... red
... green
... blue
...
>>> Colour.red
Colour(name='red', value='red')
>>> Colour.red.name
'red'
>>> Colour.red[1]
'red'
>>> list(Colour.items())
[Colour(name='red', value='red'), Colour(name='green', value='green'), Colour(name='blue', value='blue')]
>>> isinstance(Colour.red, Colour)
True
As before, you can specify the values
:
>>> class Weekday(Enum, values=from_one):
... monday, tuesday, wednesday, thursday, friday, saturday, sunday
...
>>> Weekday.monday.value
1
>>> Weekday.tuesday
Weekday(name='tuesday', value=2)
The two points of view are consistent - you can mix and match as you please.
OK, so there is a little more below. But in most cases the above should be all you need.
If you have a name, or value, you can get the tuple by calling the class:
>>> Weekday('wednesday')
Weekday(name='wednesday', value=3)
>>> Weekday(value=4).name
thursday
The values
parameter expects a no-argument function (called once per class
definition), which returns a second function from names to values.
So, for example, to give random values:
>>> from random import random
>>> def random_values():
... def value(name):
... return random()
... return value
...
>>> class Random(Enum, values=random_values):
... a, b, c
...
>>> list(Random.items())
[Random(name='a', value=0.49267653329514594), Random(name='b', value=0.5521902021074088), Random(name='c', value=0.5540234367417308)]
If you want to specify the values explicitly, use implicit=False
:
>>> class Favourite(Enum, implicit=False):
... food = 'bacon'
... number = 7
...
>>> Favourite.food.value
bacon
>>> Favourite['number']
7
You can even go wild and mix things up (here we're using bit-fields via bits
):
>>> class Emphasis(Enum, values=bits, implicit=False):
... with implicit:
... underline, italic, bold
... bold_italic = italic | bold
...
>>> Emphasis.bold_italic.value
6
By default, it is an error to repeat a value:
>>> class Error(Enum, implicit=False, values=from_one):
... with implicit:
... one
... another_one = 1
...
ValueError: Duplicate value (1) for one and another_one
but you can disable the safety check with allow_aliases=True
:
>>> class MulitlingualWeekday(Enum, implicit=False, values=from_one, allow_aliases=True):
... with implicit:
... monday, tuesday, wednesday, thursday, friday, saturday, sunday
... lunes, martes, miercoles, jueves, viernes, sabado, domingo = \
... monday, tuesday, wednesday, thursday, friday, saturday, sunday
...
This creates aliases - they are valid names, but they retrieve the original tuple:
>>> MulitlingualWeekday.lunes
MulitlingualWeekday(name='monday', value=1)
>>> MulitlingualWeekday('martes')
MulitlingualWeekday(name='tuesday', value=2)
>>> MulitlingualWeekday['miercoles']
3
Some time ago I wrote an intemperate rant about the standard Enum for Python 3.
Afterwards, I felt guilty. So, to atone myself, I started to modify the code, adding features that I felt missing from the original. The result was bnum.
But, as I worked on bnum, I came to see that I was not producing the consistent, elegant design that I was advocating. Instead, I was adding features to an already over-complex project.
So, after three weeks of work, I stopped. The next day I wrote this.
This project differs from PEP-0345's Enum in two important ways.
-
The typical end-user will notice that the API defaults to implicit values. This is because I feel the most common case for an Enum requires nothing more than a set of names. That case should be as simple as possible.
-
The Enum expert will see that I have made no effort to support other types of enumeration (alternatives to named tuples) through inheritance. In my career as a software engineer I have made many mistakes. All too often those mistakes involved inheritance. This design reflects that experience.
In addition, this code supports alternative implicit values (eg. bit fields), has no support for the "functional" form, and, by default, flags an error on duplicates.
I realise that one day's work (even when born from three weeks of frustration) is unlikely to have captured all the subtleties of the problem; that some of the complexity of the standard Enum is justified and will, in time and with bug fixes, clutter this project. But I hope that I have found something of value in the balance of features here, and that others will appreciate the view from this particular local maximum of the design space.
One objection to the implicit value approach used here is that it can lead to confusing errors when global names are shadowed by implicit values. However, this can be ameliorated in most cases by careful implementation.
In the case of implicit classes, like:
>>> class Error1(Enum):
... a = sin(b)
...
ExplicitError: Implicit scope support simple names only - no assignment or evaluation of expressions
the values returned are not the implicit values used, but Explode
instances which generate the given error on any access. A similar error
is triggered by assignment.
The approach above, using a modified value, cannot be used for with
contexts, where the value might be used later. But with
contexts provide
a separate mechanism for detecting and modifying errors. So here, for
example, a TypeError
is detected and replaced:
>>> class Error2(Enum, implicit=False):
... with implicit:
... a = sin(b)
...
ExplicitError: Implicit scope support simple names only - no assignment or evaluation of expressions
The consistency of the two viewpoints (dict and named tuples) hinges on
the method dict.items()
, which returns (name, value)
tuples. These
are both named tuples and the dictionary contents.
Implicit values are generated by providing a default value for missing class dictionary contents. This shadows global names so cannot be used to evaluate expressions - but a simple list of names does not require any evaluation.
Most of my Python programming (which I admit may be influenced by functional languages) uses common, standard data structures. An enumeration - an immutable set of names, with an optional associated set of values - does not require anything beyond that. So "what is a good design?" reduces to "how best can I fit enumerations into existing structures?" A little consideration gives two ways to associate names and values: in a dictionary, or as pairs. A little more consideration shows the two can be combined succinctly. Hence the design.
Have a simple list of names in "class" form:
>>> class Colour(Enum):
... red
... green
... blue
Detect a stupid mistake:
>>> class Error(Enum, values=from_one):
... with implicit:
... one
... two
... three = 2
...
ValueError: Duplicate value (2) for two and three
Define bit fields:
>>> class IntEmphasis(Enum, values=bits):
... underline
... italic
... bold
...
>>> allowed_styles = IntEmphasis.italic.value | IntEmphasis.bold.value
Thanks to Ethan Furman, who graciously shared his code and so educated me on the subtleties of the Python meta-class protocol.
Duncan Booth provided the implicit values hack and the motivation to question authority.
(c) 2013 Andrew Cooke, [email protected]; released into the public domain for any use, but with absolutely no warranty.