Welcome, guest | Sign In | My Account | Store | Cart

There are quite a few 'enum' recipes already here. This one is short, cheap, and has neither bells nor whistles :)

Python, 7 lines
1
2
3
4
5
6
7
def enum(typename, field_names):
    "Create a new enumeration type"

    if isinstance(field_names, str):
        field_names = field_names.replace(',', ' ').split()
    d = dict((reversed(nv) for nv in enumerate(field_names)), __slots__ = ())
    return type(typename, (object,), d)()

You define an enumeration the same way as collections.namedtuple: with a type name, followed by a sequence of identifiers (it may be a string separated by commas and/or whitespace):

>>> STATE = enum('STATE', 'GET_QUIZ, GET_VERSE, TEACH')

It returns a (single) instance of a newly created type. The enumeration elements are integers, starting at 0, exposed as attributes. There is no way to change that: if you need specific values, look elsewhere. In fact, it is as if the enum were declared as:

# not actual code
class STATE(object):
  __slots__ = ()
  GET_QUIZ, GET_VERSE, TEACH = range(3)

STATE = STATE()

The enumeration elements are read-only:

>>> STATE.GET_VERSE
1
>>> STATE.GET_VERSE = 8
Traceback (most recent call last):
  ...
AttributeError: 'STATE' object attribute 'GET_VERSE' is read-only
>>> del STATE.GET_VERSE
Traceback (most recent call last):
  ...
AttributeError: 'STATE' object attribute 'GET_VERSE' is read-only

This recipe has been tested with Python 2.4 ranging up to 3.1

13 comments

Stephen Chappell 14 years, 1 month ago  # | flag

Thank you for writing this properly! It will definitely get pasted into the module that was using enum. :-)

Stephen Chappell 14 years, 1 month ago  # | flag

Thank you! Your recipe is perfect. I was wondering, though: there is __slots__ = (), and the documentation does not appear to have anything that states what this means. Do you have a link to the Python docs that tells was assigning an empty tuple to __slots__ is supposed to do? I think I understand what is happening but would like confirmation from the documentation itself.

Gabriel Genellina (author) 14 years, 1 month ago  # | flag

See __slots__ in the Language Reference: http://docs.python.org/reference/datamodel.html#slots

In this case, __slots__ is (ab)used as a quick way to make the enumeration elements read-only, or at least avoid naïve errors.

Gabriel Genellina (author) 14 years, 1 month ago  # | flag

Oh, and it's far from being perfect! There are many other recipes out there with more features -- this one is just short and cheap. Thanks anyway... :)

Stephen Chappell 14 years, 1 month ago  # | flag

"If defined in a new-style class, __slots__ reserves space for the declared variables and prevents the automatic creation of __dict__ and __weakref__ for each instance." So you defined __slots__ as an empty tuple and the rest of that statement went into effect? If that is true, that would explain why you did not have to set __slots__ equal to the variable names being defined.

Gabriel Genellina (author) 14 years, 1 month ago  # | flag

Why __slots__: Note that enumeration elements are defined as class attributes; instances of the newly created class have no attributes at all (due to __slots__=()), they're as small as any object could be in Python:

>>> sys.getsizeof(STATE)
8
>>> sys.getsizeof(object())
8

(In this case this is mostly irrelevant - it does not make sense to have more than a single instance, so the memory usage should not be a concern.)

In addition, setting __slots__ makes the enumeration (almost) read only, for free.

Anand 14 years ago  # | flag

Why not simplify the whole code ?

Here is a shorter form of the above recipe, this time having 2 types of Enums, one with positional arguments and default values and other with user supplied values.

# Plain Enum with default positional values
def Enum(name, *args):
    args_m = [arg for arg in args if type(arg)==str]
    return type(name, (object,), dict(((v,k) for k, v in enumerate(args_m)), __slots__=args_m))() 

# Enum with keyword values
Enum_kw=lambda name, **kwargs: type(name, (object,), [item for item in  (kwargs.update({'__slots__':  [k for k in kwargs]}), kwargs)][1])()


e=Enum('STATE','A','B','C','D')
print e.A, e.B, e.C
# e.A=1    # Can't do this
# e.X=100   # Can't do this 

e=Enum_kw('STATE',A=1,B=2,C=3)
print e.A, e.B, e.C
# Can't change existing attribute
# e.A=10
# Can't assign new attributes
# e.X=100

The keyword enum is more of hack, perhaps abusing list comprehensions, but I wanted it to be a one-liner desperately :)

I don't understand the need for one liners, but if you like them, be my guest.

Setting __slots__ as in your recipe is unnecessary and wastes memory; see comment #6 above.

Stephen Chappell 14 years ago  # | flag

Thanks to Genellina, the code listed above inspired the following usage in the module down below:

def enum(names):
    """Generate an enumeration with the given names.

    The names should be separated with commas and/or whitespace.
    A class is dynamically generated, and an instance is returned.
    Python does not support enumerations, so this is an alternative."""
    names = names.replace(',', ' ').split()
    space = dict((reversed(pair) for pair in enumerate(names)), __slots__=())
    return type('enum', (object,), space)()

################################################################################

class State:

    """Oversee state transitions in a VerseMatch session.

    This class controls clients' movements from state to state while
    navigating through their sessions. Commands are verified along with
    any arguments that they take. The VerseMatch servlet automatically
    creates State objects and adds them as attributes to Session objects."""

    # These are different states instances may be in.
    OPTIONS = enum('GET_QUIZ, GET_VERSE, TEACH, CHECK')
    ...
Martin Miller 13 years, 9 months ago  # | flag

Both of Anand's versions can be shortened slightly even more, based on the comments above about setting just setting __slots__ = ().

# Plain Enum with default positional values (modified version)
def Enum(name, *args):
    args_m = [arg for arg in args if type(arg)==str]
    return type(name, (object,), dict(((v,k) for k, v in enumerate(args_m)), __slots__=()))() 

# Enum with keyword values (modified version)
Enum_kw=lambda name, **kwargs: type(name, (object,), [item for item in (kwargs.update({'__slots__': ()}), kwargs)][1])()

This is trivial, but I thought I'd mention it since I took the time to read all the comments and figure-out how Amand's worked...

Steven W. Orr 12 years, 6 months ago  # | flag

Sorry to bother. I really like the suggested implementations here. But I'm having problems running them.

I am have a problem figuring out why I can't run them. I ran the first one on the top of this page and the last one from Martin Miller. In both cases, I get the same result:

I tried this for the first sample

if __name__ == '__main__':
    Colors = enum('Colors', 'red,blue,green,yellow')
    print 'Colors:', Colors
    print 'type(Colors):', type(Colors)
    print 'dir(Colors):', dir(Colors)
    colors = Colors()

and I get:

Colors: <__main__.Colors object at 0x7f78c09450a0>
type(Colors): <class '__main__.Colors'>
 dir(Colors): ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'blue', 'green', 'red', 'yellow']
Traceback (most recent call last):
   File "./enum.py", line 16, in <module>`
   colors = Colors()
TypeError: 'Colors' object is not callable

For the last one (where the class name is Enum instead of enum):

if __name__ == '__main__':
    Colors = Enum('Colors', 'red','blue','green','yellow')
    print 'Colors:', Colors
    print 'type(Colors):', type(Colors)
    print 'dir(Colors):', dir(Colors)
    colors = Colors()`

the result is

Colors: <__main__.Colors object at 0x7fdb78c840a0>
type(Colors): <class '__main__.Colors'>
dir(Colors): ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'blue', 'green', 'red', 'yellow']
Traceback (most recent call last):
    File "./enum2.py", line 19, in <module>
       colors = Colors()
    TypeError: 'Colors' object is not callable`

In both cases, the function returns a class. I should be able to instantiate that class and assign it as an instance. I happen to be running 2.6.2.

Can someone tell me what I'm doing wrong?

delciotorres 10 years ago  # | flag

How would you check if an identifier is part of the enum?

Example:

if STATE.GET_VERSE in STATE:
Stephen Chappell 9 years, 6 months ago  # | flag

Four and a half years later, this is the function typically used in my projects:

def enum(names):
    "Create a simple enumeration having similarities to C."
    return type('enum', (), dict(map(reversed, enumerate(
        names.replace(',', ' ').split())), __slots__=()))()

Now that Python has an enum module, maybe it is time to work with it instead.

Created by Gabriel Genellina on Thu, 28 Jan 2010 (MIT)
Python recipes (4591)
Gabriel Genellina's recipes (9)

Required Modules

  • (none specified)

Other Information and Tasks