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

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)

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'

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

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

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
| Need | Use |
|---|---|
| A fixed value in one module | TAX_RATE = 0.0825 |
| Values shared across files | A constants.py module |
| Catch reassignment before it runs | TAX_RATE: Final = 0.0825 + mypy |
| A set of named options | class Status(Enum) |
| A fixed collection | tuple or frozenset |
| A read-only dictionary | MappingProxyType(...) |
| Grouped settings that can’t change | @dataclass(frozen=True) |
| Secrets and per-machine settings | Environment variables |
If you’re tidying up how a project stores its values, these guides help:
- Python naming conventions
- Variables in Python
- Python variable scope
- Mutable vs immutable in Python
- Environment variables in Python
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.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn