Constants in Python: How to Define and Use Them

Constants in Python are a convention, not a language feature: you name a variable in UPPER_CASE at module level, and everyone agrees not to change it. Python won’t stop a reassignment, but typing.Final lets a type checker catch one:

from typing import Final

TAX_RATE: Final = 0.0825        # Austin's combined sales tax
FREE_SHIPPING_OVER: Final = 50.00

One screenshot below shows mypy catching a Final violation that Python itself lets through; I used Python 3.12.5 and mypy 2.4.0 for everything here.

You’ll see the naming convention, a constants module, Final, Enum for related values, and ways to make a value genuinely read-only when a convention isn’t enough.

How do you declare a constant in Python?

Write the name in capitals with underscores, at the top of the module. Readers and linters treat it as fixed:

TAX_RATE = 0.0825            # UPPER_CASE says "don't change me"
FREE_SHIPPING_OVER = 50.00
MAX_LOGIN_ATTEMPTS = 3

def total(price):
    shipping = 0 if price >= FREE_SHIPPING_OVER else 7.99
    return round(price * (1 + TAX_RATE) + shipping, 2)

print(f"total for $40: ${total(40):.2f}")
print(f"total for $80: ${total(80):.2f}")

TAX_RATE = 0.09              # ...but Python lets you change it anyway
print(f"after reassigning: ${total(80):.2f}")

Output:

total for $40: $51.29
total for $80: $86.60
after reassigning: $87.20
Command Prompt output of Python UPPER_CASE constants used in a total function, then reassigned without any error
The convention works until someone reassigns TAX_RATE. Python allowed it silently.

There’s no const keyword, so the capitals are a promise rather than a rule. Python naming conventions covers the rest of PEP 8’s naming style.

Putting constants in a separate constants.py file

For anything bigger than one script, I keep constants in their own module and import them where they’re needed:

"""Store-wide settings. Import these; don't edit them at runtime."""
TAX_RATE = 0.0825
FREE_SHIPPING_OVER = 50.00
SUPPORTED_STATES = ("TX", "CO", "WA")
from constants import FREE_SHIPPING_OVER, SUPPORTED_STATES, TAX_RATE

print("tax rate         :", TAX_RATE)
print(f"free shipping at : ${FREE_SHIPPING_OVER:.2f}")
print("we ship to       :", ", ".join(SUPPORTED_STATES))

Output:

tax rate         : 0.0825
free shipping at : $50.00
we ship to       : TX, CO, WA

One file means one place to change a value, and an import line makes it obvious where each constant comes from. Collections like the supported states are tuples, so they can’t be changed by accident either.

Values that differ between your laptop and production, like API URLs and keys, don’t belong in constants.py at all. Environment variables in Python covers how to read those.

Using typing.Final to protect a constant

Final marks a name that shouldn’t be reassigned. Python itself ignores it at runtime, but a type checker such as mypy treats a reassignment as an error:

from typing import Final

TAX_RATE: Final = 0.0825

TAX_RATE = 0.09              # Python allows it at runtime...
print("runtime value:", TAX_RATE)

Command Prompt output:

runtime value: 0.09
final_demo.py:5: error: Cannot assign to final name "TAX_RATE"  [misc]
Found 1 error in 1 file (checked 1 source file)
Command Prompt showing a Python Final constant reassigned at runtime and mypy reporting cannot assign to final name TAX_RATE
Python ran the reassignment. mypy flagged line 5: Cannot assign to final name "TAX_RATE".

That’s the realistic level of protection in Python: your editor and CI catch the mistake before it ships. Most editors with Python support show the same warning as you type.

Using Enum for a group of related constants

When constants belong together, such as order statuses, an Enum groups them under one name and, unlike plain variables, refuses to let a member be reassigned:

from enum import Enum

class OrderStatus(Enum):
    PENDING = "pending"
    SHIPPED = "shipped"
    DELIVERED = "delivered"

print(OrderStatus.SHIPPED, "->", OrderStatus.SHIPPED.value)
print([s.name for s in OrderStatus])

try:
    OrderStatus.SHIPPED = "lost"
except AttributeError as err:
    print("AttributeError:", err)

Output:

OrderStatus.SHIPPED -> shipped
['PENDING', 'SHIPPED', 'DELIVERED']
AttributeError: cannot reassign member 'SHIPPED'
Command Prompt output of a Python Enum of order statuses, listing member names and raising AttributeError when a member is reassigned
Members have a name and a value, and reassigning one raises AttributeError.

Enums also catch typos: OrderStatus.SHIPED fails immediately, while a misspelled string like "shiped" slips through unnoticed.

How to make a truly read-only constant in Python

Reassigning a name can’t be blocked, but you can stop the value from being changed. Use immutable types for the data itself:

from dataclasses import FrozenInstanceError, dataclass
from types import MappingProxyType

STATES = ("TX", "CO", "WA")                          # a tuple can't be changed
STATE_TAX = MappingProxyType({"TX": 0.0625, "CO": 0.029})   # a read-only dict view

try:
    STATE_TAX["TX"] = 0
except TypeError as err:
    print("TypeError:", err)

@dataclass(frozen=True)
class StoreConfig:
    tax_rate: float = 0.0825
    currency: str = "USD"

config = StoreConfig()
try:
    config.tax_rate = 0.09
except FrozenInstanceError as err:
    print("FrozenInstanceError:", err)

print(STATES, dict(STATE_TAX), config)

Output:

TypeError: 'mappingproxy' object does not support item assignment
FrozenInstanceError: cannot assign to field 'tax_rate'
('TX', 'CO', 'WA') {'TX': 0.0625, 'CO': 0.029} StoreConfig(tax_rate=0.0825, currency='USD')
Command Prompt output of a Python MappingProxyType read-only dictionary and a frozen dataclass both refusing changes
The read-only dict view and the frozen dataclass both refuse changes.

A tuple instead of a list, frozenset instead of set, MappingProxyType for a dict, and a frozen dataclass for grouped settings. Mutable vs immutable in Python explains why these types can’t change.

Python’s built-in constants: True, False and None

A few names really are constant. True, False and None are keywords, so assigning to them doesn’t even compile:

True = 1

Traceback:

  File "C:\pyguides\assign_true.py", line 1
    True = 1
    ^^^^
SyntaxError: cannot assign to True
Command Prompt showing Python SyntaxError cannot assign to True
Python rejects the file before running it.

Modules provide others, such as math.pi, math.e and math.inf. Those are ordinary names, though, so the convention of not reassigning them applies just as it does to yours.

Python constants: which approach to use

NeedUse
A fixed value in one moduleTAX_RATE = 0.0825
Values shared across filesA constants.py module
Catch reassignment before it runsTAX_RATE: Final = 0.0825 + mypy
A set of named optionsclass Status(Enum)
A fixed collectiontuple or frozenset
A read-only dictionaryMappingProxyType(...)
Grouped settings that can’t change@dataclass(frozen=True)
Secrets and per-machine settingsEnvironment variables

If you’re tidying up how a project stores its values, these guides help:

Frequently asked questions

Does Python have constants?

Not enforced ones. By convention, an UPPER_CASE module-level name is a constant. typing.Final lets a type checker flag reassignment.

How do I declare a constant in Python?

Assign it in UPPER_CASE at the top of a module, such as TAX_RATE = 0.0825. Add : Final if you use a type checker.

Does typing.Final prevent changes at runtime?

No. Python ignores it when the code runs; tools like mypy and most editors report the reassignment instead.

Where should I put constants in a Python project?

At the top of the module that uses them, or in a shared constants.py imported where needed. Keep secrets in environment variables instead.

Should I use Enum or plain constants?

Use Enum for a fixed set of related options, like statuses or sizes. Use plain constants for standalone values like a rate or a limit.

How do I make a constant list in Python?

Use a tuple, which can’t be changed: STATES = ("TX", "CO"). The typing.Final documentation covers marking the name itself.