env.dev

Constants in Python

Constants by convention, typing.Final, and enum

Constants by Convention

Python does not have a built-in mechanism for declaring true constants — there is no const keyword that prevents reassignment. Instead, Python relies on a naming convention: variables written in SCREAMING_SNAKE_CASE are understood by the community to be constants that should not be modified. This convention is documented in PEP 8, the official Python style guide.

python
# Module-level constants (by convention)
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30.0
API_BASE_URL = "https://api.example.com/v1"
SUPPORTED_FORMATS = ("png", "jpg", "webp")  # tuple for immutability

Since Python constants are enforced only by convention, nothing prevents a developer from reassigning MAX_RETRIES = 999 at runtime. This is a deliberate design choice in Python — the language trusts developers to respect conventions. Linting tools like Pylint and Ruff can flag reassignment of uppercase variables to catch accidental mutations.

typing.Final (Python 3.8+)

Python 3.8 introduced typing.Final, which signals to type checkers that a variable should not be reassigned. Mypy, Pyright, and other type checkers enforce this at analysis time, though it has no runtime effect.

python
from typing import Final

MAX_CONNECTIONS: Final = 100
API_VERSION: Final[str] = "v2"
FEATURE_FLAGS: Final[dict[str, bool]] = {"new_ui": True, "beta_api": False}

# MAX_CONNECTIONS = 200  # Mypy error: Cannot assign to final name

Final works with both module-level variables and class attributes. When used in a class, it prevents subclasses from overriding the attribute. This makes it particularly useful for configuration values in class-based settings.

Enum Constants

Python's enum module (standard library since Python 3.4) provides proper constant enumerations with value safety, iteration, and membership testing. Enum members are singleton instances that cannot be reassigned or duplicated.

python
from enum import Enum, IntEnum, auto

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

class HttpStatus(IntEnum):
    OK = 200
    NOT_FOUND = 404
    SERVER_ERROR = 500

class Priority(Enum):
    LOW = auto()
    MEDIUM = auto()
    HIGH = auto()

# Usage
status = HttpStatus.OK
print(status.value)  # 200
print(status.name)   # "OK"

# Exhaustive matching with match/case (Python 3.10+)
match status:
    case HttpStatus.OK:
        print("Success")
    case HttpStatus.NOT_FOUND:
        print("Not found")

For numeric constants that need to participate in arithmetic, use IntEnum or IntFlag. For string constants, use StrEnum (Python 3.11+). For constants that should never be compared with plain integers or strings, use plain Enum.

Built-in Constants

Python provides several built-in constants and many more through the standard library. The math module provides mathematical constants, sys exposes interpreter limits, and modules like string provide useful character sets.

python
import math
import sys
import string

# Math constants
math.pi      # 3.141592653589793
math.e       # 2.718281828459045
math.tau     # 6.283185307179586 (2 * pi)
math.inf     # Positive infinity
math.nan     # Not a Number

# System constants
sys.maxsize       # Largest possible list index (2^63 - 1 on 64-bit)
sys.float_info    # Named tuple with float limits
sys.version_info  # Python version as named tuple

# String constants
string.ascii_lowercase  # 'abcdefghijklmnopqrstuvwxyz'
string.digits           # '0123456789'
string.punctuation      # '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

# Built-in constants
True, False  # Boolean constants
None         # Null/nil equivalent
...          # Ellipsis (used in type hints and slicing)
__debug__    # True unless running with -O flag

Immutable Data Structures

When defining constants that are collections, prefer immutable types. Use tuples instead of lists, frozensets instead of sets, and consider types.MappingProxyType for read-only dictionary views:

python
from types import MappingProxyType

# Immutable sequence
ALLOWED_EXTENSIONS = ("py", "pyi", "pyx")

# Immutable set
RESERVED_WORDS = frozenset({"if", "else", "while", "for", "def", "class"})

# Read-only dict view
_CONFIG = {"debug": False, "log_level": "info"}
CONFIG = MappingProxyType(_CONFIG)
# CONFIG["debug"] = True  # TypeError: does not support item assignment

Common Patterns

  • Define module-level constants at the top of the file, after imports and before class or function definitions.
  • Use typing.Final for type-checked immutability and SCREAMING_SNAKE_CASE for visual convention — both together provide the strongest signal.
  • Prefer tuples over lists and frozensets over sets for constant collections, since they are hashable and inherently immutable.
  • Use dataclasses.dataclass(frozen=True) or NamedTuple for constant records with named fields.