Skip to main content

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
caution

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"
tip

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'
tip

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
note

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

MethodRuntime ImmutabilityType Checker SupportPython VersionBest For
UPPER_CASE convention❌ (convention only)AllSimple projects, small teams
typing.Final❌ (static only)3.8+Projects using mypy or Pyright
namedtupleAllGrouped, related constants
dataclass(frozen=True)3.7+Typed, structured constants
Enum3.4+Fixed sets of named values

Best Practices

  1. Always use UPPER_CASE names: regardless of which technique you choose, the naming convention makes intent clear at a glance.
  2. Store constants in a dedicated module: create a constants.py or config.py file and import values where needed:
    # constants.py
    PI = 3.14159
    MAX_RETRIES = 5
    # main.py
    from constants import PI, MAX_RETRIES
  3. Don't use magic numbers: replace unexplained literal values with named constants to improve readability.
  4. 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.Final adds static analysis protection without changing runtime behavior.
  • namedtuple and dataclass(frozen=True) provide true runtime immutability for grouped constants.
  • Enum is 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.