Home Articles Categories Series
Pythonise Just now

Pythons Enum module

Harnessing the power of Pythons enumerations and exploring the enum module


Article Posted on by in Python
Julian Nash · a month ago in Python

Enums (also known as enumerations) are a powerful and useful feature of the Python programming language, allowing us to bind and group unique, constant values to an object.

I've found enums are great for defining and controlling constant values under a common grouping, such as any set of values needing tighter controls, uniqueness and a useful programmatic or human interface.

Whilst Python's enums aren't immutable, their members cannot be reassigned, giving us some comfort that the values we define aren't going to be tampered with!

The enum syntax

To declare an Enum in Python, we use the class syntax, inheriting from Enum:

from enum import Enum

class EnumName(Enum):

    UNIQUE_ONE = "One"
    UNIQUE_TWO = "Two"
    UNIQUE_THREE = "Three"

Key points

  • Enums consist of members and values
  • Member names must be unique
  • It's best practice use UPPERCASE when naming members
  • Values can be any Python object
  • Enums are iterable!

A simple enum

To begin working with Python's Enum, we first need to import it from the enum module:

from enum import Enum

In Python, we define enums using the well known class syntax and inheriting Enum. Members are declared as attributes of the class:

class RequestMethod(Enum):

    GET = "get"
    POST = "post"
    PUT = "put"
    PATCH = "patch"
    DELETE = "delete"

As mentioned, Enum member names must be unique...

class EnumError(Enum):

    NOT_UNIQUE = True
    NOT_UNIQUE = True

If we try and run this, we get the following error:

TypeError: Attempted to reuse key: 'NOT_UNIQUE'

Member access

To access a member of an Enum, we use dot notation - ClassName.MEMBER:

>>> RequestMethod.GET 
<RequestMethod.GET: 'get'> 

You'll notice, we don't just get the value as you may expect, instead, we get a human readable string representation.

To get the actual value, we need to append .value to our query:

>>> RequestMethod.GET.value 
'get' 

Similarly, we can access the member name by calling .name on the member:

>>> RequestMethod.GET.name 
'GET' 

Enum members can also be accessed by their value using a slightly different syntax, demonstrated below:

>>> class WebFramework(Enum): 
...     FLASK = "Flask" 
...     DJANGO = "Django" 
...     QUART = "Quart" 
...  
>>> WebFramework("Django") 
<WebFramework.DJANGO: 'Django'> 
>>> WebFramework("Django").value 
'Django' 

Rather than use dot notation, we pass the value itself to the Enum class, with the syntax EnumName(member_value) - returning a member object

Be warned, you will get an error if the value doesn't exist:

>>> WebFramework("Bottle")
ValueError: 'Bottle' is not a valid WebFramework 

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
ValueError: 'Bottle' is not a valid WebFramework

Iteration

By default, enum objects are iterable, meaning we can loop through them like a list, tuple or set:

>>> for member in RequestMethod: 
...     print(member) 
RequestMethod.GET    
RequestMethod.POST   
RequestMethod.PUT    
RequestMethod.PATCH  
RequestMethod.DELETE 

Again, to access the member values, we simply append .value to the member:

>>> for member in RequestMethod: 
...     print(member.value) 
get    
post   
put    
patch  
delete 

Calling list() and passing it an enum, returns a list of member objects:

class Languages(Enum):

    python = "Python"
    javascript = "JavaScript"
    java = "Java"
list(Languages)
[<Languages.python: 'Python'>, <Languages.javascript: 'JavaScript'>, <Languages.java: 'Java'>]

List comprehensions are a great way to get a list of member values:

>>> member_values = [i.vaue for i in Languages]
>>> member_values
['Python', 'JavaScript', 'Java']

Class methods

It's often helpful to define methods on an Enum to provide an extra layer of functionality.

Defining a staticmethod or classmethod on the class allows us to address the enum class itself, either by name or by the cls keyword respectively.

We'll define a function that returns a list of our enum member values, starting with the staticmethod syntax:

class RequestMethod(Enum):

    GET = "get"
    POST = "post"
    PUT = "put"
    PATCH = "patch"
    DELETE = "delete"

    @staticmethod
    def to_list():
        return [i.value for i in RequestMethod]

Calling the to_list method:

>>> RequestMethod.to_list() 
['get', 'post', 'put', 'patch', 'delete'] 

And now the same method using the staticmethod syntax:

class RequestMethod(Enum):

    GET = "get"
    POST = "post"
    PUT = "put"
    PATCH = "patch"
    DELETE = "delete"

    @classmethod
    def to_list(cls):
        return [i.value for i in cls]

Calling this function again yields the same result:

>>> RequestMethod.to_list() 
['get', 'post', 'put', 'patch', 'delete'] 

You'll notice we're not passing self as an argument in these two methods, as with enums, self refers to the member which the method is being called on, for example:

class Numbers(Enum):

    ONE = 1
    TWO = 2
    THREE = 3

    def squared(self):
        return self.value ** 2

    def cubed(self):
        return self.value ** 3

Each of the methods defined in the Numbers enum take self as an argument and can be called on one of the enum members, with the member itself being the self value:

>>> Numbers.TWO.squared()
4
>>> Numbers.THREE.cubed()
27

Uniqueness

Whilst enum member names must always be unique, their values can be the same.

To provide an additional layer of uniqueness, the enum module provides a unique decorator, which can be used to enforce unique member values:

from enum import Enum, unique

@unique
class RequestMethods(Enum):

    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    PATCH = "PATCH"
    DELETE = "DELETE"

If we try and define a value that already exists, Python will raise an exception. For example, if we changed the value of the DELETE member to PATCH:

ValueError: duplicate values found in <enum 'RequestMethods'>: DELETE -> PATCH

IntEnum

IntEnum provides an additional layer of control when defining an enum, enforcing member values may only be integers, or can be parsed to an integer.

from enum import IntEnum


class HTTPResponseCategory(IntEnum):

    INFO = 100
    OK = 200
    REDIRECT = 300
    BAD_REQUEST = 400
    INTERNAL_SERVER_ERROR = 500

String or boolean values can also be used as member values, providing they can be parsed to an integer:

from enum import IntEnum


class ExampleIntEnum(IntEnum):

    ZERO = False
    ONE = True
    TWO = "2"
    THREE = 3.0

The unique decorator can also be combined with IntEnum:

from enum import IntEnum, unique


@unique
class HTTPResponseCategory(IntEnum):

    INFO = 100
    OK = 200
    REDIRECT = 300
    BAD_REQUEST = 400
    INTERNAL_SERVER_ERROR = 500

Further reading

For more examples and features of the Python enum package, including Flag, IntFlag and auto, head over to the official documentation:

  • https://docs.python.org/3/library/enum.html
Did you find this article useful?
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
Contents
Loading...