coding
environment variables
python

How can I access environment variables in Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Environment variables are a standard way to pass configuration into a Python program without hardcoding values into source files. They are commonly used for secrets, API endpoints, feature flags, and runtime settings that change between development, testing, and production.

Reading Environment Variables

The standard library module for this is os. The most common APIs are os.environ and os.getenv.

Use os.environ when you want dictionary-like access:

python
1import os
2
3home = os.environ["HOME"]
4print(home)

This raises KeyError if the variable does not exist, which is useful when the variable is mandatory.

Use os.getenv when a missing variable is acceptable:

python
1import os
2
3log_level = os.getenv("LOG_LEVEL", "INFO")
4print(log_level)

This returns the default value if the variable is unset. In practice, many applications combine both styles:

  • required secrets use os.environ[...]
  • optional settings use os.getenv(..., default)

A Practical Configuration Pattern

It is usually better to read environment variables once and normalize them in one place instead of scattering os.getenv throughout the codebase.

For example:

python
1import os
2
3
4def read_settings():
5    return {
6        "app_env": os.getenv("APP_ENV", "development"),
7        "port": int(os.getenv("PORT", "8000")),
8        "debug": os.getenv("DEBUG", "false").lower() == "true",
9        "database_url": os.environ["DATABASE_URL"],
10    }
11
12
13settings = read_settings()
14print(settings)

This approach has two benefits:

  • startup fails early if a required variable is missing
  • type conversion happens in one predictable place

Without that step, it is easy to forget that environment variables are always strings and accidentally compare "8000" to 8000 or "false" to False.

Setting and Deleting Variables Inside Python

You can also modify the current process environment:

python
1import os
2
3os.environ["MODE"] = "test"
4print(os.getenv("MODE"))
5
6del os.environ["MODE"]
7print(os.getenv("MODE"))

This only affects:

  • the current Python process
  • child processes started after the change

It does not permanently update your shell, system settings, or future terminal sessions.

That distinction matters a lot. Developers often expect os.environ["X"] = "1" to change their terminal environment globally, but it does not.

Passing Variables to Child Processes

When you launch another process from Python, it inherits environment variables unless you override them.

python
1import os
2import subprocess
3
4env = os.environ.copy()
5env["GREETING"] = "hello"
6
7subprocess.run(
8    ["python3", "-c", "import os; print(os.getenv('GREETING'))"],
9    env=env,
10    check=True,
11)

This is useful for tests, build scripts, and wrappers around CLI tools.

If you do not pass env=..., the child process usually inherits the current environment automatically. Passing a copied and modified dictionary gives you tighter control.

Using .env Files During Development

In local development, many teams store configuration in a .env file and load it with python-dotenv. That is convenient, but it is still separate from the operating system environment until your program loads it.

Example .env file:

text
APP_ENV=development
PORT=5000
DATABASE_URL=postgresql://localhost/myapp

Loading it in Python:

python
1import os
2from dotenv import load_dotenv
3
4load_dotenv()
5
6print(os.getenv("APP_ENV"))
7print(os.getenv("PORT"))

This is a development convenience, not a replacement for secure secret management in production. In production systems, environment variables are often injected by process managers, containers, CI pipelines, or cloud platforms.

Security and Validation

Environment variables are better than hardcoding secrets in source files, but they are not automatically safe. They can still leak through:

  • debug logs
  • process inspection
  • accidental dumps of configuration objects

A good pattern is to validate them and avoid printing sensitive values:

python
1import os
2
3api_key = os.environ["API_KEY"]
4
5if len(api_key) < 20:
6    raise ValueError("API_KEY looks invalid")
7
8print("API key loaded successfully")

Notice that the code confirms success without exposing the secret itself.

Common Pitfalls

The most common mistake is forgetting that all environment variables arrive as strings. If you need booleans, integers, or lists, convert them explicitly.

Another mistake is using os.environ["NAME"] for optional settings. That raises KeyError and can make local development annoying. Use os.getenv with a default when the value is optional.

Developers also assume changes to os.environ persist after the script ends. They do not. The change is local to the current process and its children.

Finally, avoid committing .env files with real secrets. A .env file is convenient, but it is still plain text and should be handled carefully.

Summary

  • Use os.environ for required variables and os.getenv for optional ones.
  • Environment variables are always strings, so convert them to the types you need.
  • Changes to os.environ affect only the current process and child processes.
  • '.env files are useful for development when paired with python-dotenv.'
  • Validate configuration early and avoid printing secret values into logs.

Course illustration
Course illustration

All Rights Reserved.