How can I represent an 'Enum' in Python?

I'm mainly a C# developer, but I'm currently working on a project in Python.

How can I represent the equivalent of an Enum in Python?


Solution 1:

Enums have been added to Python 3.4 as described in PEP 435. It has also been backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4 on pypi.

For more advanced Enum techniques try the aenum library (2.7, 3.3+, same author as enum34. Code is not perfectly compatible between py2 and py3, e.g. you'll need __order__ in python 2).

  • To use enum34, do $ pip install enum34
  • To use aenum, do $ pip install aenum

Installing enum (no numbers) will install a completely different and incompatible version.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

or equivalently:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

In earlier versions, one way of accomplishing enums is:

def enum(**enums):
    return type('Enum', (), enums)

which is used like so:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

You can also easily support automatic enumeration with something like this:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

and used like so:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

Support for converting the values back to names can be added this way:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

This overwrites anything with that name, but it is useful for rendering your enums in output. It will throw a KeyError if the reverse mapping doesn't exist. With the first example:

>>> Numbers.reverse_mapping['three']
'THREE'

If you are using MyPy another way to express "enums" is with typing.Literal.

For example:

from typing import Literal #python >=3.8
from typing_extensions import Literal #python 2.7, 3.4-3.7


Animal = Literal['ant', 'bee', 'cat', 'dog']

def hello_animal(animal: Animal):
    print(f"hello {animal}")

hello_animal('rock') # error
hello_animal('bee') # passes

Solution 2:

Before PEP 435, Python didn't have an equivalent but you could implement your own.

Myself, I like keeping it simple (I've seen some horribly complex examples on the net), something like this ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

In Python 3.4 (PEP 435), you can make Enum the base class. This gets you a little bit of extra functionality, described in the PEP. For example, enum members are distinct from integers, and they are composed of a name and a value.

from enum import Enum

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

If you don't want to type the values, use the following shortcut:

class Animal(Enum):
    DOG, CAT = range(2)

Enum implementations can be converted to lists and are iterable. The order of its members is the declaration order and has nothing to do with their values. For example:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True

Solution 3:

Here is one implementation:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Here is its usage:

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)

Solution 4:

If you need the numeric values, here's the quickest way:

dog, cat, rabbit = range(3)

In Python 3.x you can also add a starred placeholder at the end, which will soak up all the remaining values of the range in case you don't mind wasting memory and cannot count:

dog, cat, rabbit, horse, *_ = range(100)

Solution 5:

The best solution for you would depend on what you require from your fake enum.

Simple enum:

If you need the enum as only a list of names identifying different items, the solution by Mark Harrison (above) is great:

Pen, Pencil, Eraser = range(0, 3)

Using a range also allows you to set any starting value:

Pen, Pencil, Eraser = range(9, 12)

In addition to the above, if you also require that the items belong to a container of some sort, then embed them in a class:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

To use the enum item, you would now need to use the container name and the item name:

stype = Stationery.Pen

Complex enum:

For long lists of enum or more complicated uses of enum, these solutions will not suffice. You could look to the recipe by Will Ware for Simulating Enumerations in Python published in the Python Cookbook. An online version of that is available here.

More info:

PEP 354: Enumerations in Python has the interesting details of a proposal for enum in Python and why it was rejected.