Work with Environment Variables
Learn how to read, set, and manage environment variables using Python's os.environ module in this practical tutorial.
Focus: work with environment variables and os.environ
Have you ever hardcoded API keys, database URLs, or secret tokens directly into your Python scripts? It's a security nightmare and a maintenance headache — every time those values change, you have to dig into your code and hope you didn't miss a spot. Environment variables solve this problem by keeping sensitive configuration outside your source code, letting you change settings without touching a single line of Python. In this lesson, you'll learn how to work with environment variables using Python's built-in os.environ dictionary — the standard way to read, set, and even delete these powerful settings across your operating system.
The problem this lesson solves
Modern applications depend on countless configuration values: API endpoints, secret keys, database credentials, feature flags, and more. When you embed them in your code, you create several dangerous problems:
- Security risks — Anyone with access to your repository can see your secrets.
- Portability issues — Your code might work on your machine but fail on a server with different paths or ports.
- Hard-coded failures — A simple typo or environment change requires a code redeploy.
- Testing nightmares — Switching between development, staging, and production means rewriting constants.
Environment variables are the industry-standard way to decouple configuration from code. They're supported by every operating system, every CI/CD pipeline, and every cloud platform. By mastering os.environ, you'll write cleaner, safer, and more portable Python applications.
Core concept / mental model
Think of os.environ as a live dictionary that your operating system maintains. When your Python program starts, it inherits a snapshot of all currently set environment variables from the parent process (your shell, IDE, or system service). You can read from this dictionary at any time, and if you make changes (in most cases), those changes only affect your running process — not the system globally.
What is os.environ?
os.environ is a mapping object (similar to a Python dictionary) that represents the environment variables of the current process. Each key is a variable name (always a string), and each value is its string value. It's available from the standard library module os, so no extra installs are needed.
Pro tip: Unlike a regular dictionary,
os.environbehaves like a dictionary but with OS-level side effects. When you modify it, the change is immediately reflected in your current process and any child processes you spawn.
Key characteristics
- Keys and values are always strings — If you need numbers or booleans, you must convert them manually.
- Case-sensitive on most systems —
MY_VARandmy_varare different variables on Linux/macOS (though Windows is case-insensitive). - Immutable at the OS level for the current process — You can't truly delete a variable from the OS; you can only unset it for your running program.
- Thread-safe to read but not thread-safe to modify — If you have multiple threads writing to
os.environ, wrap modifications in a lock.
How it works step by step
Let's walk through the lifecycle of using environment variables in Python:
Step 1: Import the os module
Every environment variable interaction requires importing os:
import os
Step 2: Read an environment variable
The most common operation: access the value using its key. If the variable doesn't exist, Python raises a KeyError — just like a missing dictionary key.
# Safe way: use .get()
db_url = os.environ.get('DATABASE_URL')
if db_url is None:
print('Warning: DATABASE_URL not set. Using default.')
db_url = 'postgres://localhost:5432/mydb'
# Or with a default value inline
secret_key = os.environ.get('SECRET_KEY', 'default-dev-key')
Step 3: Set an environment variable for the current process
This is useful for configuring your program dynamically:
os.environ['MY_APP_MODE'] = 'development'
# Now your code can check os.environ['MY_APP_MODE']
Step 4: Delete (unset) an environment variable
Use del or pop() to remove a variable from your process's environment:
if 'TEMP_SECRET' in os.environ:
del os.environ['TEMP_SECRET']
# Or use pop (returns the value or a default)
ol_value = os.environ.pop('TEMP_SECRET', None)
Step 5: List all environment variables
Sometimes you need to inspect the whole environment (for debugging or logging):
for key, value in sorted(os.environ.items()):
print(f'{key}={value}')
Hands-on walkthrough
Now you'll write a complete script that reads, sets, and validates environment variables. Create a file named env_demo.py:
import os
def get_config():
"""Read required configuration from environment variables."""
config = {
'API_KEY': os.environ.get('MY_API_KEY'),
'DATABASE_URL': os.environ.get('DATABASE_URL'),
'DEBUG_MODE': os.environ.get('DEBUG', 'false').lower() == 'true',
'PORT': int(os.environ.get('PORT', 8000)),
}
# Validate required variables
missing = [k for k, v in config.items() if v is None and k != 'DEBUG_MODE']
if missing:
raise EnvironmentError(f"Missing required env vars: {', '.join(missing)}")
return config
# Example usage
if __name__ == '__main__':
# Set a local env var for this run
os.environ['MY_API_KEY'] = 'sk-test-12345'
os.environ['DATABASE_URL'] = 'postgres://user:pass@localhost/mydb'
os.environ['DEBUG'] = 'true'
try:
cfg = get_config()
print('Configuration loaded successfully:')
for key, value in cfg.items():
print(f' {key}: {value}')
except EnvironmentError as e:
print(f'Error: {e}')
Expected output:
Configuration loaded successfully:
API_KEY: sk-test-12345
DATABASE_URL: postgres://user:pass@localhost/mydb
DEBUG_MODE: True
PORT: 8000
Now run the same script without setting the environment variables (comment out the four os.environ lines) — you'll see the error raised because MY_API_KEY and DATABASE_URL are missing.
Practice: check if a variable exists
import os
if 'PATH' in os.environ:
print('PATH is set.')
paths = os.environ['PATH'].split(os.pathsep)
print(f'You have {len(paths)} directories in your PATH.')
else:
print('PATH is not set — unusual.')
Expected output (varies):
PATH is set.
You have 8 directories in your PATH.
Compare options / when to choose what
There are a few ways to manage environment variables in Python. Here's a comparison to help you choose:
| Approach | Use case | Pros | Cons |
|---|---|---|---|
os.environ[key] |
Simple read/write | Built-in, no dependencies, immediate effect | Only strings, KeyError if missing |
os.environ.get(key, default) |
Safe read | No exception, easy defaults | Still strings, no type conversion |
os.getenv(key, default) |
Read-only | Slightly shorter, same as .get() |
Cannot set values |
.env file + python-dotenv |
Development settings | Easy to share with teammates, keeps secrets out of repo | Extra library, not native |
Third-party config libraries (e.g., pydantic-settings) |
Complex configs | Type coercion, validation, schema | Heavier dependency, learning curve |
When to choose what:
- Use os.environ or os.getenv for quick scripts or when you need to modify the environment dynamically.
- Use a .env file with python-dotenv for projects with multiple environment variables that differ per developer (make sure to add .env to .gitignore).
- Use pydantic-settings for large applications requiring strict validation and nested configuration (e.g., FastAPI projects).
Troubleshooting & edge cases
1. KeyError when reading a missing variable
# This crashes:
db_url = os.environ['DATABASE_URL'] # KeyError if not set
Fix: Always use .get() with a default, or check 'DATABASE_URL' in os.environ first.
2. Setting a variable doesn't persist after script ends
# Inside a script:
os.environ['TEMP_VAR'] = 'hello'
# After script exits, os.environ reverts to the shell's original state
This is by design — environment variable changes are not permanent. To persist, you must modify shell configuration files (.bashrc, .zshrc) or system-level settings.
3. Type coercion fails silently
os.environ['PORT'] = 'abc'
port = int(os.environ['PORT']) # ValueError: invalid literal for int()
Fix: Always validate after conversion, or use a default with a try/except.
4. Large environment slows down startup
On some operating systems, if you have thousands of environment variables (rare but possible), iterating over os.environ can be slow. Only iterate when necessary.
5. Environment variables inherited from parent
If you run your script from a shell with certain variables set, your Python script inherits them automatically. Be aware that running your script via sudo or in a container may have a different environment.
What you learned & what's next
In this lesson, you learned:
- How to read, set, and delete environment variables using os.environ.
- That os.environ is a dictionary-like mapping that always stores strings.
- The difference between os.environ[key], .get(), and os.getenv().
- How to validate required environment variables and handle missing ones gracefully.
- When to use plain os.environ versus third-party libraries like python-dotenv.
You're now ready to write configuration-aware Python scripts that can run anywhere — from your laptop to a production server. The next lesson in this track builds on this foundation: Working with configuration files (e.g., YAML or JSON). You'll learn how to read and merge settings from environment variables, config files, and command-line arguments for even more robust applications.
Practice recap
Write a small script that reads a LOG_LEVEL environment variable (default to 'INFO'), then configures Python's logging module accordingly. Then set the variable in your shell before running the script and verify the log level changes. This exercise will solidify your understanding of reading environment variables and using them to influence program behavior.
Common mistakes
- Using
os.environ[key]without checking existence — always use.get()orinto avoid KeyError. - Assuming environment variable values are typed (e.g., treating 'True' as boolean) — they are always strings; convert explicitly.
- Modifying
os.environin a multithreaded script without a lock — concurrent modifications can cause data races. - Forgetting that changes to
os.environare only visible to the current process and any child processes, not the parent shell or other running programs.
Variations
- Use
os.getenv(key, default)as a read-only alternative toos.environ.get()— slightly shorter but cannot set values. - Load variables from a
.envfile withpython-dotenvfor development — keeps secrets out of version control but requires an extra library. - For type-safe configuration, use
pydantic-settings(or olderenvparse) to automatically parse and validate environment variables into typed Python objects.
Real-world use cases
- Storing database credentials (host, port, user, password) in environment variables so the same code works in dev, staging, and production without changes.
- Setting feature flags (e.g.,
ENABLE_NEW_FEATURE=true) to toggle behavior in deployed applications without redeploying code. - Configuring cloud service endpoints and API keys in containerized environments (Docker/Kubernetes) where secrets are injected at runtime.
Key takeaways
- Environment variables keep configuration outside code, improving security and portability.
os.environis a dictionary-like object where all keys and values are strings — always convert types manually.- Always use
.get()with a default to read variables safely and avoid KeyError. - Changes to
os.environaffect only the current process and its children, not the system globally. - For projects with many variables, consider a
.envfile withpython-dotenvbut never commit secrets to version control.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.