OS Module

Python Modules

Python os Module

In this tutorial, we'll use the os module to read environment settings, work with folders, and find information about the operating system and current process.

Python's os module provides an interface to the operating system. It can read environment variables, inspect directories, rename files, walk directory trees, and expose information about the current process.

It is part of the standard library:

import os

For object-oriented file paths, prefer [pathlib](/tutorials/modules/pathlib). Use os when you need environment variables, process information, low-level operating-system features, or an API that naturally returns strings and file descriptors.

The current working directory

The current working directory controls how relative paths are interpreted:

import os

print(os.getcwd())

It is normally the directory from which the program was launched, not necessarily the directory containing the Python file.

Change it with os.chdir():

import os

os.chdir("reports")
print(os.getcwd())

Changing the working directory affects the whole process. In reusable code, passing an explicit path is usually clearer than relying on chdir().

Reading environment variables

Environment variables hold configuration outside your source code. They are commonly used for deployment modes, API endpoints, and feature settings.

Use os.getenv() when a value may be missing:

import os

mode = os.getenv("APP_MODE", "development")
print(mode)

The second argument is the default. Without it, a missing variable produces None.

os.environ behaves like a mapping:

import os

os.environ["APP_MODE"] = "testing"
print(os.environ["APP_MODE"])

Reading a missing key with os.environ["NAME"] raises KeyError. Use this deliberately when the program must refuse to start without the setting.

Environment variables are always strings:

import os

port = int(os.getenv("PORT", "8000"))
debug = os.getenv("DEBUG", "false").lower() == "true"

Do not commit passwords or API keys to source control. Environment variables can keep secrets out of code, although production secrets should ideally be supplied by a dedicated secret-management system.

Listing directory contents

os.listdir() returns names inside a directory:

import os

for name in os.listdir("."):
    print(name)

The returned names do not include the parent path, and their order is not guaranteed.

os.scandir() returns richer directory entries and can be more efficient when you also need file information:

import os

with os.scandir(".") as entries:
    for entry in entries:
        if entry.is_file() and entry.name.endswith(".py"):
            print(entry.name)

Use the context manager so the underlying directory resource is closed promptly.

Creating directories

Create one directory with os.mkdir():

import os

os.mkdir("output")

It fails if the directory exists or its parent is missing.

os.makedirs() can create a whole directory chain:

import os

os.makedirs("output/reports/2026", exist_ok=True)

exist_ok=True allows the final directory to already exist.

Joining paths with os.path

Never assume every operating system uses / as its path separator. os.path.join() combines components correctly:

import os

path = os.path.join("reports", "2026", "july.csv")
print(path)

Other commonly used helpers include:

import os

path = "reports/july.csv"

print(os.path.basename(path))
print(os.path.dirname(path))
print(os.path.splitext(path))
print(os.path.exists(path))
print(os.path.abspath(path))

For new code, Path.name, Path.parent, Path.suffix, Path.exists(), and Path.resolve() are usually more readable.

Walking a directory tree

os.walk() visits a directory and its descendants:

import os

for root, directories, files in os.walk("project"):
    for filename in files:
        if filename.endswith(".py"):
            full_path = os.path.join(root, filename)
            print(full_path)

Each iteration supplies:

  • root: the directory currently being visited
  • directories: its immediate child directory names
  • files: its immediate file names

You can modify directories in place to skip parts of the tree:

directories[:] = [
    name for name in directories
    if name not in {".git", ".venv", "node_modules"}
]

Renaming, replacing, and deleting

The following operations change the real filesystem:

import os

os.rename("draft.txt", "published.txt")
os.remove("old-report.txt")
os.rmdir("empty-folder")

os.rmdir() only removes an empty directory. This safeguard helps prevent accidental recursive deletion.

os.replace(source, destination) replaces an existing destination where the operating system supports it:

os.replace("new-settings.json", "settings.json")

Always resolve the intended paths and handle errors before destructive operations. Common exceptions include FileNotFoundError, FileExistsError, PermissionError, and OSError.

Operating-system and process information

os.name gives a broad platform family:

import os

print(os.name)
print(os.sep)
print(os.getpid())

Typical os.name values are "posix" for Linux and macOS or "nt" for Windows. Use [sys.platform](/tutorials/modules/sys) when you need a more specific platform identifier.

os.cpu_count() reports the number of logical CPUs available to the system, or None when Python cannot determine it:

workers = os.cpu_count() or 1

It is not automatically the ideal number of workers for every program. Workload, memory, container limits, and external services all matter.

Running other programs

Although os.system() exists, the subprocess module gives you better control over arguments, errors, input, and output:

import subprocess

result = subprocess.run(
    ["python", "--version"],
    capture_output=True,
    text=True,
    check=True,
)

print(result.stdout or result.stderr)

Passing an argument list avoids many shell-quoting problems. Never build shell commands by concatenating untrusted user input.

A practical example: read required configuration

import os


def load_config():
    try:
        database_url = os.environ["DATABASE_URL"]
    except KeyError as error:
        raise RuntimeError("DATABASE_URL must be set") from error

    return {
        "database_url": database_url,
        "port": int(os.getenv("PORT", "8000")),
        "debug": os.getenv("DEBUG", "false").lower() == "true",
    }

Required and optional settings are treated differently, and string values are converted at the boundary.

Common os mistakes

  • Assuming the current working directory is the script's directory.
  • Treating environment variables as integers or booleans without conversion.
  • Using os.environ["KEY"] when a missing value should be allowed.
  • Concatenating paths manually.
  • Assuming directory listings are sorted.
  • Changing the global working directory inside a library function.
  • Using os.system() with untrusted input.
  • Deleting or renaming paths without checking the exact target.

Practice

1. Read a PORT environment variable and convert it to an integer with a default of 8000. 2. List only the .txt files in a directory using os.scandir(). 3. Walk a project tree while skipping .git and .venv directories. 4. Create a nested output directory safely. 5. Explain when pathlib is clearer than os.path.

Explore more standard-library tasks in the [interactive Python coding exercises](/codingexercises).