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
Enum
s 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 packagethe 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 mydbf
package next time I tinker with it.Disclosure: I am the author of the Python stdlib
Enum
, theenum34
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 Enum
1:
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.
Enum
s 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
Repeat Rows in a Pandas Dataframe Based on Column Value
How to Remove Packages Installed with Python's Easy_Install
How to Dynamically Compose an or Query Filter in Django
Character Reading from File in Python
How to Find Char in String and Get All the Indexes
How to Remove/Delete a Folder That Is Not Empty
Databaseerror: Current Transaction Is Aborted, Commands Ignored Until End of Transaction Block
Why Is Semicolon Allowed in This Python Snippet
Calculate Area of Polygon Given (X,Y) Coordinates
Pylab.Ion() in Python 2, Matplotlib 1.1.1 and Updating of the Plot While the Program Runs
How to Check the Versions of Python Modules
Django Db Settings 'Improperly Configured' Error
Binary Representation of Float in Python (Bits Not Hex)
Python: Simple List Merging Based on Intersections