Python Enum, When and Where to Use

Python, what's the Enum type good for?

What's the purpose of enums? What value do they create for the language? When should I use them and when should I avoid them?

The Enum type got into Python via PEP 435. The reasoning given is:

The properties of an enumeration are useful for defining an immutable, related set of constant values that may or may not have a semantic meaning.

When using numbers and strings for this purpose, they could be characterized as "magic numbers" or "magic strings". Numbers rarely carry with them the semantics, and strings are easily confused (capitalization? spelling? snake or camel-case?)

Days of the week and school letter grades are examples of this kind of collections of values.

Here's an example from the docs:

from enum import Enum

class Color(Enum):
red = 1
green = 2
blue = 3

Like the bare class, this is much more readable and elegant than the namedtuple example, it is also immutable, and it has further benefits as we'll see below.

Strictly dominant: The type of the enum member is the enum

>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True

This allows you to define functionality on the members in the Enum definition. Defining functionality on the values could be accomplished with the other prior methods, but it would be very inelegant.

Improvement: String coercion

The string representation is human readable, while the repr has more information:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

I find this to be an improvement over the magic numbers and even possibly better than strings from the namedtuple.

Iteration (parity):

The enum supports iteration (like the namedtuple, but not so much the bare class) too:

>>> for color in Color:
print(color)
Color.red
Color.green
Color.blue

The __members__ attribute is an ordered mapping of the names of the enums to their respective enum objects (similar to namedtuple's _asdict() function).

>>> Color.__members__
mappingproxy(OrderedDict([('red', <Color.red: 1>), ('green', <Color.green: 2>),
('blue', <Color.blue: 3>)]))

Supported by pickle (parity)

You can serialize and deserialize the enum (in case anyone was worried about this):

>>> import pickle
>>> color.red is pickle.loads(pickle.dumps(color.red))
True

Improvement: Aliases

This is a nice feature that the bare class doesn't have, and it would be difficult to tell the alias was there in the namedtuple.

class Color(Enum):
red = 1
green = 2
blue = 3
really_blue = 3

The alias comes after the canonical name, but they are both the same:

>>> Color.blue is Color.really_blue
True

If aliases should be prohibited to avoid value collisions, use the enum.unique decorator (a strictly dominant feature).

Strictly dominant: comparisons done with is

The enum is intended to be tested with is, which is a fast check for a single object's identity in the process.

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

Tests for equality work as well, but tests for identity with is are optimal.

Different semantics from other Python classes

Enum classes have different semantics from regular Python types. The values of the Enum are instances of the Enum, and are singletons in memory for those values - there is no other purpose for instantiating them.

>>> Color.red is Color(1)

This is important to keep in mind, perhaps it is a downside, but comparing on this dimension is comparing apples with oranges.

Enums not assumed to be ordered

While the Enum class knows what order the members are created in, enums are not assumed to be ordered. This is a feature because many things that may be enumerated have no natural order, and therefore order would be arbitrary.

However, you can give your enums order (see the next section).

Subclassing

You can't subclass an Enum with members declared, but you can subclass an Enum that doesn't declare members to share behavior (see the OrderedEnum recipe in the docs).

This is a feature - it makes little sense to subclass an Enum with members, but again, the comparison is apples and oranges.

When should I use enum.Enum?

This is the new canonical enumeration in Python. Collaborators will expect your enums to behave like these enums.

Use it anywhere you have a canonical source of enumerated data in your code where you want explicitly specified to use the canonical name, instead of arbitrary data.

For example, if in your code you want users to state that it's not "Green", "green", 2, or "Greene", but Color.green - use the enum.Enum object. It's both explicit and specific.

There are a lot of examples and recipes in the documentation.

When should I avoid them?

Stop rolling your own or letting people guess about magic numbers and strings. Don't avoid them. Embrace them.

However, if your enum members are required to be integers for historic reasons, there's the IntEnum from the same module, which has the same behavior, but is also an integer because it subclasses the builtin int before subclassing Enum. From IntEnum's help:

class IntEnum(builtins.int, Enum)

we can see that the IntEnum values would test as an instance of an int.

Python Enum, when and where to use?

1. When and where to use enums?

  • When you have a variable that takes one of a limited set of possible values.

For example, the days of the week:

class Weekday(Enum):
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
SATURDAY = 6
SUNDAY = 7

2. Why do we need enum? What are the advantages?

  • Enums are advantageous because they give a name to a constant, which makes code more readable; and because the individual members cannot be rebound, making Python Enums semi-constant (because the Enum itself could still be rebound).

  • Besides more readable code, debugging is also easier as you see a name along with the value, not just the value

  • Desired behavior can be added to Enums

For example, as anyone who has worked with the datetime module knows, datetime and date have two different representations for the days of the week: 0-6 or 1-7. Rather than keep track of that ourselves we can add a method to the Weekday enum to extract the day from the datetime or date instance and return the matching enum member:

    @classmethod
def from_date(cls, date):
return cls(date.isoweekday())

3. What exactly is Enum?

  • Enum is a type, whose members are named constants, that all belong to (or should) a logical group of values. So far I have created Enums for:

    - the days of the week
    - the months of the year
    - US Federal Holidays in a year

FederalHoliday is my most complex; it uses this recipe, and has methods to return the actual date the holiday takes place on for the year given, the next business day if the day in question is a holiday (or the range of days skipped includes the holiday or weekends), and the complete set of dates for a year. Here it is:

class FederalHoliday(AutoEnum):
NewYear = "First day of the year.", 'absolute', Month.JANUARY, 1
MartinLutherKingJr = "Birth of Civil Rights leader.", 'relative', Month.JANUARY, Weekday.MONDAY, 3
President = "Birth of George Washington", 'relative', Month.FEBRUARY, Weekday.MONDAY, 3
Memorial = "Memory of fallen soldiers", 'relative', Month.MAY, Weekday.MONDAY, 5
Independence = "Declaration of Independence", 'absolute', Month.JULY, 4
Labor = "American Labor Movement", 'relative', Month.SEPTEMBER, Weekday.MONDAY, 1
Columbus = "Americas discovered", 'relative', Month.OCTOBER, Weekday.MONDAY, 2
Veterans = "Recognition of Armed Forces service", 'relative', Month.NOVEMBER, 11, 1
Thanksgiving = "Day of Thanks", 'relative', Month.NOVEMBER, Weekday.THURSDAY, 4
Christmas = "Birth of Jesus Christ", 'absolute', Month.DECEMBER, 25

def __init__(self, doc, type, month, day, occurrence=None):
self.__doc__ = doc
self.type = type
self.month = month
self.day = day
self.occurrence = occurrence

def date(self, year):
"returns the observed date of the holiday for `year`"
if self.type == 'absolute' or isinstance(self.day, int):
holiday = Date(year, self.month, self.day)
if Weekday(holiday.isoweekday()) is Weekday.SUNDAY:
holiday = holiday.replace(delta_day=1)
return holiday
days_in_month = days_per_month(year)
target_end = self.occurrence * 7 + 1
if target_end > days_in_month[self.month]:
target_end = days_in_month[self.month]
target_start = target_end - 7
target_week = list(xrange(start=Date(year, self.month, target_start), step=one_day, count=7))
for holiday in target_week:
if Weekday(holiday.isoweekday()) is self.day:
return holiday

@classmethod
def next_business_day(cls, date, days=1):
"""
Return the next `days` business day from date.
"""
holidays = cls.year(date.year)
years = set([date.year])
while days > 0:
date = date.replace(delta_day=1)
if date.year not in years:
holidays.extend(cls.year(date.year))
years.add(date.year)
if Weekday(date.isoweekday()) in (Weekday.SATURDAY, Weekday.SUNDAY) or date in holidays:
continue
days -= 1
return date

@classmethod
def year(cls, year):
"""
Return a list of the actual FederalHoliday dates for `year`.
"""
holidays = []
for fh in cls:
holidays.append(fh.date(year))
return holidays

Notes:

  • Date is from my dbf package

  • the enhanced xrange (supporting a range of dates) is also custom, but I don't think I have included it anywhere; I'll stuff it in my dbf package next time I tinker with it.

  • Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

How can I represent an 'Enum' in Python?

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

Best way to use Python Enum or Dictionary for mapping constants and staying DRY with inference

The immediate solution to your problem is to inherit from str as well as from Enum1:

class CompassDirection(str, Enum):
NORTH = 'NORTH'

>>> print(CompassDirection.NORTH == 'NORTH')
True

The long-term solution is to realize that you should no longer be using strings, but the enum members, and using is:

somevar = CompassDirections.NORTH
#
# some intervening code
#
print(somevar is CompassDirections.NORTH)
# True

You should only have to deal with the string "NORTH" if getting input from a user, a configuration file, etc., and then you'll want to immediately convert to an Enum:

result = input('Enter direction')
result = CompassDirection(result.upper())

1 Python 3.11 will have a StrEnum type, which will ensure that the values are already of type str.


Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

What is the difference between using a enum and simple class variable call in Python?

The Enum type comes from languages like C and C++. The motivation is explained e.g. here:

An enum is a user-defined type consisting of a set of named constants called enumerators. The idea is that instead of using an int to represent a set of values, a type with a restricted set of values is used instead. ... The problem with [using integers for representing rainbow colors] is that there are many more ints than colors. If violet has the value 7, and the program assigns a value of 15 to a variable, then it is clearly a bug - but might not be detected, as 15 is a valid value for an int. ... Using enums increase the level of abstraction and lets the programmer think about what the values mean rather than worry about how they are stored and accessed. This reduces the occurrence of bugs.

Enums have these benefits:

  • They restrict the values that the enum variable can take.
  • They force you to think about all the possible values that the enum can take.
  • They are a constant rather than a number, increasing readability of the source code

Traffic lights are a nice example:

from enum import Enum   

class TrafficLights(Enum):
red = 4 # 4 is the bit representation
red_yellow = 6
green = 1
yellow = 2

print(TrafficLights.yellow)
print(TrafficLights(2))
print(repr(TrafficLights.yellow))

# Looking at object attributes
print(dir(TrafficLights.yellow))

# Accessing value by name
print(TrafficLights.yellow.value)

for light in TrafficLights:
print(light)

Note that the traffic lights have a particular bit representation, and cannot take any other values. Also, the class is iterable and the items can be accessed by name or by value, which is not possible with regular attributes.

Bottom line, enum is not an essential module, but is nice to have. It can be useful for specific use cases, where a limited set of enumerated objects is required.

How can I access an enum member with an input in python

You can try using getattr:

from enum import Enum
class Animals(Enum):
Dog = 1
Cat = 2
Cow = 3

Choose = input('Choose an animal')

print(getattr(Animals, Choose).value)

Output:

1

getattr stands for "get attribute", which means it gets the variable in the class which it's name is what the second argument is.

Enums have already builtin __getitem__ methods, so you can directly index it with [] brackets, like this:

print(Animals[Choose].value)

Output:

1

Python - How to get Enum value by index

IIUC, you want to do:

from enum import Enum

class days_of_the_week(Enum):
monday = 0
tuesday = 1
wednesday = 2
thursday = 3
friday = 4
saturday = 5
sunday = 6

>>> days_of_the_week(1).name
'tuesday'


Related Topics



Leave a reply



Submit