How to Create Constants in Python
Unlike languages such as Java or C++, Python does not have a built-in const keyword to declare constants. However, constants are a fundamental part of writing clean, maintainable code: they communicate that a value should never change after it's defined.
This guide explores the conventional approach to defining constants in Python and several techniques that enforce immutability to varying degrees, helping you choose the right strategy for your project.
The Convention: UPPER_CASE Variable Names
The most common and widely recognized way to define constants in Python is by using ALL_UPPERCASE names with underscores. This is a naming convention documented in PEP 8, Python's official style guide.
PI = 3.14159
GRAVITY = 9.8
SPEED_OF_LIGHT = 299792458
MAX_CONNECTIONS = 100
print(PI)
print(SPEED_OF_LIGHT)
Output:
3.14159
299792458
This is only a convention. Python will not prevent you from reassigning the value:
PI = 3.14159
PI = 3.14 # No error: Python allows this
print(PI)
Output:
3.14
The uppercase naming simply signals to other developers (and your future self) that the variable is intended to be constant. It's your responsibility to avoid modifying it.
While the naming convention is sufficient for most projects, there are several techniques that provide stronger guarantees of immutability.
Using typing.Final (Python 3.8+)
Starting with Python 3.8, you can annotate a variable with Final from the typing module. This tells static type checkers like mypy that the variable should not be reassigned.
from typing import Final
PI: Final = 3.14159
GRAVITY: Final = 9.8
SPEED_OF_LIGHT: Final = 299792458
print(f"PI: {PI}")
print(f"Gravity: {GRAVITY}")
Output:
PI: 3.14159
Gravity: 9.8
What Happens If You Reassign?
At runtime, Python will not raise an error: Final is a hint, not a runtime enforcement.
from typing import Final
PI: Final = 3.14159
PI = 3.14 # No runtime error
print(PI)
Output:
3.14
However, running mypy on this code will catch the issue:
$ mypy script.py
script.py:4: error: Cannot assign to final name "PI"
Combine Final with the uppercase naming convention for maximum clarity. Even if your team doesn't use a type checker, the annotation serves as strong documentation:
from typing import Final
MAX_RETRIES: Final = 3
API_BASE_URL: Final = "https://api.example.com"
Using namedtuple for Grouped Constants
A namedtuple creates a lightweight, immutable object. This is an excellent choice when you have a set of related constants that belong together logically.
from collections import namedtuple
Constants = namedtuple("Constants", ["pi", "gravity", "speed_of_light"])
PHYSICS = Constants(pi=3.14159, gravity=9.8, speed_of_light=299792458)
print(f"PI: {PHYSICS.pi}")
print(f"Gravity: {PHYSICS.gravity}")
print(f"Speed of Light: {PHYSICS.speed_of_light}")
Output:
PI: 3.14159
Gravity: 9.8
Speed of Light: 299792458
Attempting to Modify a Value
Because namedtuple instances are immutable, trying to change an attribute raises an AttributeError:
PHYSICS.pi = 3.14 # ❌ Raises AttributeError
Output:
AttributeError: can't set attribute
This provides true runtime immutability, making namedtuple one of the strongest options for constants in Python.
Using @dataclass(frozen=True) for Immutable Constants
Python 3.7 introduced dataclasses. Setting frozen=True makes instances immutable, any attempt to modify an attribute raises a FrozenInstanceError.
from dataclasses import dataclass
@dataclass(frozen=True)
class PhysicsConstants:
pi: float = 3.14159
gravity: float = 9.8
speed_of_light: int = 299792458
PHYSICS = PhysicsConstants()
print(f"PI: {PHYSICS.pi}")
print(f"Gravity: {PHYSICS.gravity}")
print(f"Speed of Light: {PHYSICS.speed_of_light}")
Output:
PI: 3.14159
Gravity: 9.8
Speed of Light: 299792458
Attempting to Modify a Value
PHYSICS.pi = 3.14 # ❌ Raises FrozenInstanceError
Output:
dataclasses.FrozenInstanceError: cannot assign to field 'pi'
Frozen dataclasses are ideal when you want type annotations, default values, and immutability all in one clean structure.
Using Enum for a Set of Named Constants
The enum module is perfect when your constants represent a fixed set of related values, such as status codes, directions, or configuration options.
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class HttpStatus(Enum):
OK = 200
NOT_FOUND = 404
SERVER_ERROR = 500
print(Color.RED)
print(Color.RED.value)
print(HttpStatus.NOT_FOUND.value)
Output:
Color.RED
1
404
Attempting to Modify a Value
Color.RED = 99 # ❌ Raises AttributeError
Output:
AttributeError: cannot reassign member 'RED'
Enums are inherently immutable and also support iteration, comparison, and serialization, making them a powerful choice for constant sets.
Using __slots__ and a Custom Class
For advanced use cases, you can create a class that blocks attribute assignment after initialization using __setattr__:
class Constants:
__slots__ = ()
PI = 3.14159
GRAVITY = 9.8
SPEED_OF_LIGHT = 299792458
CONST = Constants()
print(CONST.PI)
print(CONST.GRAVITY)
Output:
3.14159
9.8
This approach prevents adding new instance attributes, but the class-level attributes can still technically be reassigned via Constants.PI = 3.14. For full protection, combine this with __setattr__ overriding or use one of the other methods described above.
Comparison of Approaches
| Method | Runtime Immutability | Type Checker Support | Python Version | Best For |
|---|---|---|---|---|
| UPPER_CASE convention | ❌ (convention only) | ❌ | All | Simple projects, small teams |
typing.Final | ❌ (static only) | ✅ | 3.8+ | Projects using mypy or Pyright |
namedtuple | ✅ | ❌ | All | Grouped, related constants |
dataclass(frozen=True) | ✅ | ✅ | 3.7+ | Typed, structured constants |
Enum | ✅ | ✅ | 3.4+ | Fixed sets of named values |
Best Practices
- Always use UPPER_CASE names: regardless of which technique you choose, the naming convention makes intent clear at a glance.
- Store constants in a dedicated module: create a
constants.pyorconfig.pyfile and import values where needed:# constants.py
PI = 3.14159
MAX_RETRIES = 5# main.py
from constants import PI, MAX_RETRIES - Don't use magic numbers: replace unexplained literal values with named constants to improve readability.
- Choose the right level of enforcement: for solo or small-team projects, the naming convention is usually enough. For libraries or large codebases, consider
Final, frozen dataclasses, or enums.
Conclusion
While Python doesn't enforce constants at the language level, you have multiple effective strategies to define and protect them:
- UPPER_CASE naming is the universal convention and should always be used.
typing.Finaladds static analysis protection without changing runtime behavior.namedtupleanddataclass(frozen=True)provide true runtime immutability for grouped constants.Enumis the best choice for fixed sets of named, related values.
By combining clear naming conventions with the right immutability technique, you can write Python code that is safer, more readable, and easier to maintain.