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 visiteddirectories: its immediate child directory namesfiles: 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).