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
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
.
Correct way to represent an Enum with values of specific types
Python has an untagged union type called Union
. This type is considered untagged because there is no information storing which variant of the enum is selected. For your use case, here is an untagged implementation:
from typing import TypeVar, Union
T = TypeVar("T")
MatchType = Union[int, dict[str, T], list[T]]
def get() -> MatchType:
return [1, 2, 3]
def match_on(match_type: MatchType):
if isinstance(match_type, int):
print("An int.")
elif isinstance(match_type, dict):
print("A dict.")
elif isinstance(match_type, list):
print("A list.")
Notice however that we have to iterate through all possible MatchType
s during matching. This is because there is no tag stored with the variants of an untagged union that we can index a map / table by. A naive attempt to do constant-time matching might look like this:
def match_on(match_type: MatchType):
{
int: lambda: print("An int."),
dict: lambda: print("A dictionary."),
list: lambda: print("A list.")
}[type(match_type)]()
but given a subclass of int
, this would throw an IndexError
because the type isn't strictly int
.
To enable constant time matching like the rust compiler might emit for matching on a tagged union, you'd have to mimic a tagged union like this:
from dataclasses import dataclass
from typing import TypeVar, final, Generic, Union
T = TypeVar("T")
@final
@dataclass
class MatchNative:
value: int
@final
@dataclass
class MatchDictionary(Generic[T]):
value: dict[str, T]
# Avoid collision with built in type `List` by prepending `Match`.
@final
@dataclass
class MatchList(Generic[T]):
value: list[T]
MatchType = Union[MatchNative, MatchDictionary[T], MatchList[T]]
def get():
return MatchList([1, 2, 3])
def match_on(match_type: MatchType):
{
MatchNative: lambda: print("An int."),
MatchDictionary: lambda: print("A dictionary."),
MatchList: lambda: print("A list.")
}[type(match_type)]()
The @dataclass
annotations aren't required, it just creates a __init__
for the tags that I then used in the get
function.
Here, we created three classes that include the relevant data for each type, while also serving as a tag themselves because of the extra layer of indirection introduced. These classes are made @final
in order to rule out subclasses of the tags being given as an instance of the Union
. The @final
annotations enable constant-time matching.
Note that both the untagged and tagged implementations are still missing exhaustiveness checking, which rust's match
statement has. Python 3.10 is coming with a match
statement, but I haven't looked into whether mypy will be able to perform exhaustiveness checking with that.
String-based enum in Python
It seems that it is enough to inherit from str
class at the same time as Enum
:
class MyEnum(str, Enum):
state1 = 'state1'
state2 = 'state2'
The tricky part is that the order of classes in the inheritance chain is important as this:
class MyEnum(Enum, str):
state1 = 'state1'
state2 = 'state2'
throws:
TypeError: new enumerations should be created as `EnumName([mixin_type, ...] [data_type,] enum_type)`
With the correct class the following operations on MyEnum
are fine:
print('This is the state value: ' + state)
As a side note, it seems that the special inheritance trick is not needed for formatted strings which work even for Enum
inheritance only:
msg = f'This is the state value: {state}' # works without inheriting from str
Type annotations for Enum attribute
Type hinting the Color class should work:
def get_color_return_something(some_color: Color):
print(some_color.value)
Python Enum: How to get enum values with multiple attributes
The correct syntax is B(('Val', 'One'))
, passing the value of the enum directly (thus in this case a tuple), or simply naming the enum variant by name: B.ValOne
.
I must admit this is confusing, with __init__
automagically destructuring the tuple into two arguments. The error isn't helpful either.
Python - Enum Keys From Another Enum
What you really want is to map AuthCode
s to strings. The usual way to do this in Python is with a dictionary, like this:
AUTHCODE_MESSAGES = {
AuthCode.ALREADY_AUTHENTICATED: "User was already logged in",
# copy the rest of the code/message pairs here
# make sure to replace the = with :
# and add a , on the end of every line
}
You can then use it like so:
# FIXME: enums is not a very clear name for what it does.
from enums import AuthCode, AUTHCODE_MESSAGES
def authResponse(authCode):
return jsonify({
"code": authCode,
"message": AUTHCODE_MESSAGES[authCode]
})
How to use private enum values that are not accessible by Enum.__members__?
You can use a property
for this problem, like this:
class Fruits(str, Enum):
apple = "apple"
banana = "banana"
orange = "orange"
lemon = "lemon"
@property
def _citrus(self):
return {self.__class__.orange, self.__class__.lemon}
def is_citrus(self):
return self in self._citrus
print(Fruits.orange.is_citrus())
# True
print(Fruits.__members__.keys())
dict_keys(['apple', 'banana', 'orange', 'lemon'])
Related Topics
Why Does Multiprocessing Use Only a Single Core After I Import Numpy
Extracting Text from Ms Word Files in Python
How to Pass a Variable by Reference
Using Global Variables in a Function
Pass Input/Variables to Command/Script Over Ssh Using Python Paramiko
Shebang Notation: Python Scripts on Windows and Linux
How to Find the Real User Home Directory Using Python
How to Get Pid by Process Name
Python: How to Kill Child Process(Es) When Parent Dies
Run Python Script At Startup in Ubuntu
Compare Two Images the Python/Linux Way
Packaging a Python Script on Linux into a Windows Executable
Ensure a Single Instance of an Application in Linux
How to Kill a Python Child Process Created With Subprocess.Check_Output() When the Parent Dies
Converting Datetime.Date to Utc Timestamp in Python