Need a Constant in Python? Enums Can Come in Useful
Python doesn’t have constants. But it has enums
Python doesn’t have constants. You probably learnt this early on when learning Python. Unlike many other programming languages, you can’t define a constant in Python. All variables are variable!
“Ah, but there are immutable types.”
Sure, you can have an object that doesn’t change throughout its lifetime. But you can’t have a reference to it that’s guaranteed not to change. The identifier (variable name) you use to refer to this immutable type can easily switch to refer to something else.
“How about using all caps for the identifier. Doesn’t that make it a constant?”
No, it doesn’t. That’s just a convention you use to show your intent as a programmer that an identifier refers to a value that shouldn’t change. But nothing prevents that value from changing.
Here’s an all-uppercase identifier that refers to an immutable object:
The identifier is all caps. The object is a tuple, which is immutable. Recall that you don’t need parentheses to create a tuple—the comma is sufficient.
So, you use an all-uppercase identifier for an immutable object. But that doesn’t stop you from changing the value of FIXED_LOCATION:
Neither using an immutable object nor using uppercase identifiers prevents you from changing this value!
So, Python doesn’t have constants. But there are tools you can use to mimic constant behaviour depending on the use case you need. In this article I’ll explore one of these: Enums.
All The Python Coding Place video courses are included in a single, cost-effective bundle. The courses cover beginner and intermediate level courses, and you also get access to a members-only forum.
Jargon Corner: Enum is short for enumeration, and you’ll see why soon. But don’t confuse this with the built-in enumerate(), which does something else. See Parkruns, Python’s enumerate and zip, and Why Python Loops Are Different from Other Languages • [Note: This is a Club post] for more on enumerate().
Let’s revisit our friend Alex from an article from a short while ago: “AI Coffee” Grand Opening This Monday. This article explored the program Alex used in his new coffee shop and how the function signature changed over time to minimise confusion and errors when using it. It’s a fun article about all the various types and styles of parameters and arguments you can have in Python functions.
But it didn’t address another potential source of error when using this code. So let’s look at a simple version of the brew_coffee() function Alex used to serve his coffee-drinking customers:
When you call the function, you pass the coffee you want to this function:
And elsewhere in the code, these coffees are defined in a dictionary:
If you’ve written code like this in the past, you’ll know that it’s rather annoying—and error-prone—to keep using the strings with the coffee names wherever you need to refer to a specific coffee, such as when passing the coffees to brew_coffee().
The names of the coffees and the parameters that define them do not change. They’re constant. It’s a shame Python doesn’t have constants, you may think.
But it has enums…
The CoffeeType enum contains seven members. Each member has a name and a value. By convention, you use all-uppercase names for the members since they represent constants. And these enum members behave like constants:
When you attempt to reassign a value to a member, Python raises an exception:
Traceback (most recent call last):
File ..., line 12, in <module>
CoffeeType.ESPRESSO = 10
^^^^^^^^^^^^^^^^^^^
...
AttributeError: cannot reassign member ‘ESPRESSO’The member names are also contained within the namespace of the Enum class—you use CoffeeType.ESPRESSO rather than just ESPRESSO outside the Enum class definition. So, you get autocomplete, refactor-friendly names, and fewer silent typos. With raw strings, "capuccino" (with a single “p”) can sneak into your code, and nothing complains until a customer is already waiting at the counter.
For these enum members to act as constants, their names must be unique. You can’t have the same name appear more than once:
You include ESPRESSO twice with different values. But this raises an exception:
Traceback (most recent call last):
File ..., line 3, in <module>
...
ESPRESSO = 8
^^^^^^^^
...
TypeError: ‘ESPRESSO’ already defined as 1That’s good news. Otherwise, these enum members wouldn’t be very useful as constants.
However, you can have an alias. You can have more than one member sharing the same value:
The members MACCHIATO and ESPRESSO_MACCHIATO both have the value 4. Therefore, they represent the same item. They’re different names for the same coffee:
Note that Python always displays the first member associated with a value:
CoffeeType.MACCHIATOThe output says CoffeeType.MACCHIATO even though you pass CoffeeType.ESPRESSO_MACCHIATO to print().
Incidentally, if you don’t want to have aliases, you can use the @unique decorator when defining the enum class.
You can also access the name and value of an enum member:
Here’s the output from this code:
CoffeeType.ESPRESSO
ESPRESSO
1The .name attribute is a string, and the .value attribute is an integer in this case:
Here’s the output when you display the types:
<enum ‘CoffeeType’>
<class ‘str’>
<class ‘int’>You’ll often use integers as values for enum members—that’s why they’re called enumerations. But you don’t have to:
The values are now also strings:
CoffeeType.ESPRESSO
ESPRESSO
espressoYou can use these enum members instead of strings wherever you need to refer to each coffee type:
…and again when you call brew_coffee():
Now you have a safer, neater, and more robust way to handle the coffee types... and treat them as constants.
A Bit More • StrEnum and IntEnum
Let’s add some code to brew_coffee():
This version is almost fine. But here’s a small problem:
Brewing a CoffeeType.CORTADO with 30ml of coffee and
60ml of milk. Strength level: 2The output displays CoffeeType.CORTADO since coffee_type refers to an enum member. You’d like the output to just show the name of the coffee! Of course, you can use the .value attribute any time you need to fetch the string.
However, to make your coding simpler and more readable, you can ensure that the enum members are also strings themselves without having to rely on one of their attributes. You can use StrEnum instead of Enum:
Members of a StrEnum also inherit all the string methods, such as:
You call the string method .title() directly on the StrEnum member:
MacchiatoThere’s also an IntEnum that can be useful when you want your enum members to act as integers. Let’s replace the coffee strength values, which are currently integers, with IntEnum members:
You could use a standard Enum in this case. But using an IntEnum allows you to manipulate its members directly as integers should you need to do so. Here’s an example:
This code is equivalent to printing 3 + 1. You wouldn’t be able to do this with enums unless you use the .value attributes.
And A Couple More Things About Enums
Let’s explore a couple of other useful enum features before we wrap up this article.
An enum class is iterable. Here are all the coffee types in a for loop:
Note that CoffeeType is the class name. But it’s an enum (a StrEnum in this case), so it’s iterable:
Brewing a espresso with 30ml of coffee and 0ml of milk. Strength level: 3
Brewing a latte with 30ml of coffee and 150ml of milk. Strength level: 1
Brewing a cappuccino with 30ml of coffee and 100ml of milk. Strength level: 2
Brewing a macchiato with 30ml of coffee and 10ml of milk. Strength level: 3
Brewing a flat_white with 30ml of coffee and 120ml of milk. Strength level: 2
Brewing a ristretto with 20ml of coffee and 0ml of milk. Strength level: 4
Brewing a cortado with 30ml of coffee and 60ml of milk. Strength level: 2I’ll let you sort out the text displayed to make sure you get ‘an espresso’ when brewing an espresso and to remove the underscore in the flat white!
And there will be times when you don’t care about the value of an enum member. You just want to use an enum to give your constants a consistent name. In this case, you can use the automatic value assignment:
Python assigns integers incrementally in the order you define the members for Enum classes. Note that these start from 1, not 0.
The same integers are used if you use IntEnum classes. However, when you use StrEnum classes, Python behaves differently since the values should be strings in this case:
The values are now the lowercase strings representing the members’ names.
Of course, the default values you get when you use auto() may be the values you need, after all. This is the case for both enums you created in this article, CoffeeType and CoffeeStrength :
Using auto() when appropriate makes it easier to write your code and expand it later if you need to add more enum members.
Final Words
You can get by without ever using enums. But there are many situations where you’d love to reach for a constant, and an enum will do just fine. Sure, Python doesn’t have constants. But it has enums!
Code in this article uses Python 3.14
The code images used in this article are created using Snappify. [Affiliate link]
Join The Club, the exclusive area for paid subscribers for more Python posts, videos, a members’ forum, and more.
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Appendix: Code Blocks
Code Block #1
FIXED_LOCATION = 51.75, 0.34
Code Block #2
FIXED_LOCATION
# (51.75, 0.34)
FIXED_LOCATION = "Oops!"
FIXED_LOCATION
# 'Oops!'
Code Block #3
def brew_coffee(coffee_type):
# Actual code goes here...
# It's not relevant for this article
Code Block #4
brew_coffee("espresso")
brew_coffee("cappuccino")
Code Block #5
coffee_types = {
"espresso": {"strength": 3, "coffee_amount": 30, "milk_amount": 0},
"latte": {"strength": 1, "coffee_amount": 30, "milk_amount": 150},
"cappuccino": {"strength": 2, "coffee_amount": 30, "milk_amount": 100},
"macchiato": {"strength": 3, "coffee_amount": 30, "milk_amount": 10},
"flat_white": {"strength": 2, "coffee_amount": 30, "milk_amount": 120},
"ristretto": {"strength": 4, "coffee_amount": 20, "milk_amount": 0},
"cortado": {"strength": 2, "coffee_amount": 30, "milk_amount": 60},
}
Code Block #6
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
Code Block #7
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
CoffeeType.ESPRESSO = 10
Code Block #8
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
ESPRESSO = 8
Code Block #9
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = 1
LATTE = 2
CAPPUCCINO = 3
MACCHIATO = 4
FLAT_WHITE = 5
RISTRETTO = 6
CORTADO = 7
ESPRESSO_MACCHIATO = 4
Code Block #10
print(CoffeeType.ESPRESSO_MACCHIATO)
Code Block #11
# ...
print(CoffeeType.ESPRESSO)
print(CoffeeType.ESPRESSO.name)
print(CoffeeType.ESPRESSO.value)
Code Block #12
# ...
print(type(CoffeeType.ESPRESSO))
print(type(CoffeeType.ESPRESSO.name))
print(type(CoffeeType.ESPRESSO.value))
Code Block #13
from enum import Enum
class CoffeeType(Enum):
ESPRESSO = "espresso"
LATTE = "latte"
CAPPUCCINO = "cappuccino"
MACCHIATO = "macchiato"
FLAT_WHITE = "flat_white"
RISTRETTO = "ristretto"
CORTADO = "cortado"
print(CoffeeType.ESPRESSO)
print(CoffeeType.ESPRESSO.name)
print(CoffeeType.ESPRESSO.value)
Code Block #14
# ...
coffee_types = {
CoffeeType.ESPRESSO: {"strength": 3, "coffee_amount": 30, "milk_amount": 0},
CoffeeType.LATTE: {"strength": 1, "coffee_amount": 30, "milk_amount": 150},
CoffeeType.CAPPUCCINO: {"strength": 2, "coffee_amount": 30, "milk_amount": 100},
CoffeeType.MACCHIATO: {"strength": 3, "coffee_amount": 30, "milk_amount": 10},
CoffeeType.FLAT_WHITE: {"strength": 2, "coffee_amount": 30, "milk_amount": 120},
CoffeeType.RISTRETTO: {"strength": 4, "coffee_amount": 20, "milk_amount": 0},
CoffeeType.CORTADO: {"strength": 2, "coffee_amount": 30, "milk_amount": 60},
}
Code Block #15
# ...
brew_coffee(CoffeeType.CORTADO)
Code Block #16
# ...
def brew_coffee(coffee_type):
coffee_details = coffee_types.get(coffee_type)
if not coffee_details:
print("Unknown coffee type!")
return
print(
f"Brewing a {coffee_type} "
f"with {coffee_details['coffee_amount']}ml of coffee "
f"and {coffee_details['milk_amount']}ml of milk. "
f"Strength level: {coffee_details['strength']}"
)
brew_coffee(CoffeeType.CORTADO)
Code Block #17
from enum import StrEnum
class CoffeeType(StrEnum):
ESPRESSO = "espresso"
LATTE = "latte"
CAPPUCCINO = "cappuccino"
MACCHIATO = "macchiato"
FLAT_WHITE = "flat_white"
RISTRETTO = "ristretto"
CORTADO = "cortado"
# ...
def brew_coffee(coffee_type):
coffee_details = coffee_types.get(coffee_type)
if not coffee_details:
print("Unknown coffee type!")
return
print(
f"Brewing a {coffee_type} "
f"with {coffee_details['coffee_amount']}ml of coffee "
f"and {coffee_details['milk_amount']}ml of milk. "
f"Strength level: {coffee_details['strength']}"
)
brew_coffee(CoffeeType.CORTADO)
Code Block #18
print(CoffeeType.MACCHIATO.title())
Code Block #19
# ...
class CoffeeStrength(IntEnum):
WEAK = 1
MEDIUM = 2
STRONG = 3
EXTRA_STRONG = 4
coffee_types = {
CoffeeType.ESPRESSO: {
"strength": CoffeeStrength.STRONG,
"coffee_amount": 30,
"milk_amount": 0,
},
CoffeeType.LATTE: {
"strength": CoffeeStrength.WEAK,
"coffee_amount": 30,
"milk_amount": 150,
},
CoffeeType.CAPPUCCINO: {
"strength": CoffeeStrength.MEDIUM,
"coffee_amount": 30,
"milk_amount": 100,
},
# ... and so on...
}
# ...
Code Block #20
print(CoffeeStrength.STRONG + CoffeeStrength.WEAK)
Code Block #21
# ...
for coffee in CoffeeType:
brew_coffee(coffee)
Code Block #22
from enum import Enum, auto
class Test(Enum):
FIRST = auto()
SECOND = auto()
Test.FIRST
# <Test.FIRST: 1>
Test.SECOND
# <Test.SECOND: 2>
Code Block #23
from enum import StrEnum, auto
class Test(StrEnum):
FIRST = auto()
SECOND = auto()
Test.FIRST
# <Test.FIRST: 'first'>
Test.SECOND
# <Test.SECOND: 'second'>
Code Block #24
# ...
class CoffeeType(StrEnum):
ESPRESSO = auto()
LATTE = auto()
CAPPUCCINO = auto()
MACCHIATO = auto()
FLAT_WHITE = auto()
RISTRETTO = auto()
CORTADO = auto()
class CoffeeStrength(IntEnum):
WEAK = auto()
MEDIUM = auto()
STRONG = auto()
EXTRA_STRONG = auto()
# ...
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com



























