How do I create a constant in Python?
How do I declare a constant in Python?
In Java, we do:
public static final String CONST_NAME = "Name";
Python doesn’t have a built-in constant type like Java, but you can create constants by using naming conventions and language features. The standard approach is to use uppercase letters with underscores for constant names, and while Python doesn’t enforce immutability, developers typically respect these conventions by not modifying variables declared as constants.
Contents
- Understanding Python’s Approach to Constants
- Naming Conventions for Constants
- Different Ways to Create Constants
- Best Practices and Examples
- Comparison to Java Constants
- Advanced Constant Techniques
Understanding Python’s Approach to Constants
Python doesn’t have a const keyword or built-in constant mechanism like Java, C++, or other statically typed languages. Instead, Python relies on convention over configuration. The language philosophy emphasizes programmer responsibility rather than language enforcement.
# This is how you would typically declare a constant in Python
MAX_CONNECTIONS = 100
API_BASE_URL = "https://api.example.com"
DEBUG_MODE = False
The Python Enhancement Proposal 8 (PEP 8) establishes naming conventions that signal to developers that certain values should be treated as constants. While you can technically modify these values, doing so violates the convention and can lead to unexpected behavior in production code.
“We are all consenting adults here” - This Python philosophy means the language trusts developers to follow conventions rather than enforcing rules through syntax.
Naming Conventions for Constants
Following PEP 8 guidelines, constants should be:
- All uppercase letters: Use uppercase to distinguish constants from variables
- Snake case: Separate words with underscores
- Descriptive names: Choose names that clearly indicate the constant’s purpose
# Good constant naming
MAX_RETRIES = 3
DATABASE_URL = "postgresql://localhost:5432/mydb"
API_TIMEOUT = 30.0
# Avoid these patterns
maxRetries = 3 # Uses camelCase (should be for classes)
Max_Retries = 3 # Mixed case
max_retries = 3 # Lowercase (looks like a variable)
Special cases for constants:
- Constants that are class-level should use uppercase with underscores
- Constants that are module-level should also follow the same convention
- Constants that are private (intended for internal use) should start with a single underscore
# Module-level constants
DEFAULT_PORT = 8080
CACHE_TTL = 3600
# Class-level constants
class DatabaseConnection:
MAX_POOL_SIZE = 10
CONNECTION_TIMEOUT = 30
# Private constants (internal use)
_INTERNAL_API_KEY = "abc123"
Different Ways to Create Constants
1. Simple Variable Assignment (Most Common)
The simplest approach is just using uppercase variable names:
PI = 3.14159
GRAVITY = 9.81
SPEED_OF_LIGHT = 299792458
2. Using typing.Final (Python 3.8+)
Python 3.8 introduced the Final type hint to indicate that a variable should not be reassigned:
from typing import Final
MAX_USERS: Final[int] = 1000
API_ENDPOINT: Final[str] = "https://api.example.com"
While Final doesn’t prevent reassignment at runtime, it provides:
- Static type checking compatibility with tools like MyPy
- Documentation of intent
- Runtime protection in some contexts
3. Using Enums for Constants with Values
For related constants, consider using Enum:
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
# Usage
current_status = Status.ACTIVE
4. Using NamedTuple or Data Classes
For constants with multiple related values:
from typing import NamedTuple
class Config(NamedTuple):
DATABASE_URL: str
API_KEY: str
TIMEOUT: int
config = Config(
DATABASE_URL="postgresql://localhost:5432/mydb",
API_KEY="abc123",
TIMEOUT=30
)
5. Using Module-Level Constants
Create a dedicated constants module:
# constants.py
import os
# Application settings
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
SECRET_KEY = os.getenv("SECRET_KEY", "default-secret-key")
# Database settings
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///default.db")
MAX_CONNECTIONS = int(os.getenv("MAX_CONNECTIONS", "10"))
# API settings
API_VERSION = "v1"
Best Practices and Examples
Basic Constant Usage
# Configuration constants
DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3
CACHE_SIZE = 1000
# Mathematical constants
PI = 3.14159
E = 2.71828
GOLDEN_RATIO = 1.61803
# Application constants
APP_NAME = "MyApp"
VERSION = "1.0.0"
Constants in Functions
def calculate_circle_area(radius):
"""Calculate the area of a circle using the PI constant."""
return PI * radius ** 2
def fetch_with_retry(url, timeout=DEFAULT_TIMEOUT, retries=MAX_RETRIES):
"""Fetch data with retry logic."""
# Implementation here
pass
Constants in Classes
class DatabaseConnection:
# Class constants
MAX_POOL_SIZE = 10
CONNECTION_TIMEOUT = 30
DEFAULT_PORT = 5432
def __init__(self, host, port=DEFAULT_PORT):
self.host = host
self.port = port
# Connection logic here
Constants with Configuration
import os
from typing import Final
# Environment-based constants
DEBUG_MODE: Final[bool] = os.getenv("DEBUG", "False").lower() == "true"
API_BASE_URL: Final[str] = os.getenv("API_URL", "https://default.api.com")
DATABASE_URL: Final[str] = os.getenv("DATABASE_URL")
# Fallback constants
FALLBACK_DATABASE_URL = "sqlite:///fallback.db"
Comparison to Java Constants
While Java enforces constants at the language level, Python uses a different approach:
Java:
public static final String API_URL = "https://api.example.com";
public static final int MAX_USERS = 1000;
public static final boolean DEBUG_MODE = false;
Python:
API_URL = "https://api.example.com"
MAX_USERS = 1000
DEBUG_MODE = False
Key Differences:
| Feature | Java | Python |
|---|---|---|
| Enforcement | Language-level enforcement | Convention-based |
| Type Safety | Compile-time checking | Runtime flexibility |
| Multiple Types | static final for class-level |
Module-level by convention |
| Modification | Impossible at runtime | Possible but discouraged |
| Documentation | Built into language | Through naming conventions |
When You Need True Constants
If you need runtime enforcement of constants, consider these approaches:
# Using property with read-only access
class Constants:
_MAX_USERS = 1000
@property
def MAX_USERS(self):
return self._MAX_USERS
# Using namedtuple (immutable)
from collections import namedtuple
Constants = namedtuple('Constants', ['MAX_USERS', 'API_URL'])
CONSTANTS = Constants(MAX_USERS=1000, API_URL="https://api.example.com")
# Using frozen dataclass (Python 3.7+)
from dataclasses import dataclass
@dataclass(frozen=True)
class Constants:
MAX_USERS: int
API_URL: str
Advanced Constant Techniques
Constants with Validation
from typing import Final
# Constants that include validation
PORT_RANGE = range(1, 65536)
def validate_port(port: int) -> None:
if port not in PORT_RANGE:
raise ValueError(f"Port must be between 1 and 65535, got {port}")
DEFAULT_PORT: Final[int] = 8080
validate_port(DEFAULT_PORT)
Dynamic Constants
# Constants that are computed at import time
import os
import platform
SYSTEM_NAME: Final[str] = platform.system()
IS_WINDOWS: Final[bool] = platform.system() == "Windows"
IS_MACOS: Final[bool] = platform.system() == "Darwin"
IS_LINUX: Final[bool] = platform.system() == "Linux"
# Environment-based constants with defaults
API_KEY: Final[str] = os.getenv("API_KEY", "default-key")
DEBUG: Final[bool] = os.getenv("DEBUG", "False").lower() == "true"
Constants in Configuration Classes
from typing import Final, Dict, Any
class AppConfig:
"""Application configuration constants."""
# Server configuration
HOST: Final[str] = "localhost"
PORT: Final[int] = 8080
DEBUG: Final[bool] = False
# Database configuration
DB_HOST: Final[str] = "localhost"
DB_PORT: Final[int] = 5432
DB_NAME: Final[str] = "myapp"
# API configuration
API_VERSION: Final[str] = "v1"
API_RATE_LIMIT: Final[int] = 100
@classmethod
def get_database_url(cls) -> str:
"""Generate database URL from constants."""
return f"postgresql://{cls.DB_HOST}:{cls.DB_PORT}/{cls.DB_NAME}"
Constants with Documentation
# Constants with comprehensive documentation
"""
Application Constants Module
This module contains all application-wide constants used throughout the system.
Constants are defined in uppercase to indicate they should not be modified.
"""
# API constants
API_BASE_URL: Final[str] = "https://api.example.com"
API_TIMEOUT: Final[int] = 30 # seconds
API_MAX_RETRIES: Final[int] = 3
# Business logic constants
MAX_ITEMS_PER_PAGE: Final[int] = 100
DEFAULT_ITEMS_PER_PAGE: Final[int] = 20
MIN_USERNAME_LENGTH: Final[int] = 3
MAX_USERNAME_LENGTH: Final[int] = 50
# System constants
LOG_LEVEL: Final[str] = "INFO"
LOG_FILE: Final[str] = "app.log"
Conclusion
Python doesn’t enforce constants at the language level like Java, but uses a convention-based approach that’s just as effective when followed properly. The key takeaways are:
- Use uppercase naming with underscores to indicate constants (e.g.,
MAX_CONNECTIONS) - Follow PEP 8 guidelines for consistent code style across your project
- Consider
typing.Finalfor better type checking and documentation (Python 3.8+) - Group related constants in dedicated modules or classes for organization
- Document your constants to make their purpose and usage clear
- Respect the convention by not modifying declared constants, even though it’s technically possible
While Python gives you the flexibility to change “constants” at runtime, this flexibility comes with the responsibility to follow conventions that maintain code clarity and prevent bugs. The Python community’s emphasis on “consenting adults” means that developers are trusted to make good decisions about when and how to use constants.
For teams transitioning from Java to Python, the key mental shift is moving from language-enforced immutability to convention-based immutability. While the syntax is simpler in Python, the discipline required to maintain constants effectively is just as important.