The Python os module provides a portable interface to operating system functionality — file and directory operations, environment variables, process information, and POSIX system calls — abstracting platform differences between Unix and Windows.
Working with files, directories, and the OS
What you'll learn: The most commonly used os functions for path manipulation, listing directories, environment variables, and checking whether files and directories exist.
How to read this tab: Open a terminal alongside your reading. Try each function on your actual file system as you read about it.
The os module is Python's bridge to the operating system — it lets you create, read, move, and delete files and directories, read environment variables, and run system commands, all from Python code.
Use os.path.join() — never string concatenation. "data/" + "file.txt" breaks on Windows (uses backslashes). os.path.join("data", "file.txt") works on every OS. Even better: learn pathlib (next lesson) which makes path operations even cleaner with the / operator.
Working with paths
import os
# Current working directory
cwd = os.getcwd()
print(cwd) # /home/priya/projects
# Build paths portably (uses / on Unix, \ on Windows)
config_path = os.path.join(cwd, "config", "settings.json")
print(config_path) # /home/priya/projects/config/settings.json
# Path components
path = "/home/priya/data/report.csv"
print(os.path.dirname(path)) # /home/priya/data
print(os.path.basename(path)) # report.csv
print(os.path.splitext(path)) # ('/home/priya/data/report', '.csv')
print(os.path.exists(path)) # True or False
print(os.path.isfile(path)) # True if it's a file
print(os.path.isdir(path)) # True if it's a directory
print(os.path.getsize(path)) # size in bytesUse os.makedirs(path, exist_ok=True) to create nested directories. os.mkdir() fails if the parent directory doesn't exist. os.makedirs() creates all intermediate directories. exist_ok=True means it won't raise an error if the directory already exists.
Creating and listing directories
import os
# Create a single directory
os.mkdir("new_folder")
# Create nested directories (like mkdir -p)
os.makedirs("parent/child/grandchild", exist_ok=True)
# exist_ok=True: no error if already exists
# List directory contents
entries = os.listdir(".")
print(entries) # ['file1.txt', 'folder', 'script.py', ...]
# Walk entire directory tree
for dirpath, dirnames, filenames in os.walk("/home/priya/projects"):
for filename in filenames:
full_path = os.path.join(dirpath, filename)
print(full_path)
# Rename / move
os.rename("old_name.txt", "new_name.txt")
# Delete file
os.remove("old_file.txt")
# Delete empty directory
os.rmdir("empty_folder")Always use os.environ.get() with a default, not os.environ[]. os.environ["MISSING_VAR"] raises KeyError. os.environ.get("MISSING_VAR", "default") returns the default safely. Environment variables are the standard way to configure applications in production.
Environment variables
import os
# Read environment variable (None if not set)
home = os.environ.get("HOME")
db_url = os.environ.get("DATABASE_URL", "sqlite:///default.db")
# Set environment variable (only in current process)
os.environ["DEBUG"] = "true"
# All environment variables
for key, value in os.environ.items():
print(f"{key}={value}")
# Run a shell command (os.system is legacy — use subprocess instead)
exit_code = os.system("ls -la") # not recommended for production✅ Beginner tab complete — check your understanding
- I can use os.path.join() to build file paths cross-platform
- I can use os.listdir() or os.walk() to list files in a directory
- I can read and write environment variables with os.environ
- I know os.path.exists(), os.path.isfile(), os.path.isdir()
os.walk, os.scandir, and process information
What you'll learn: os.walk for recursively traversing directory trees. os.scandir as the faster, more informative alternative to os.listdir. Process information via os.getpid, os.cpu_count, and os.stat.
How to read this tab: Try os.walk on your project directory and print every file path. This pattern appears in almost every file-processing script.
For new code: use pathlib. pathlib (the next lesson) provides the same functionality as os.path but with an object-oriented, much cleaner API. You'll still encounter os.path in existing codebases, so knowing it is essential — but write new code with pathlib.
os.path vs. pathlib
os.path works with plain strings. pathlib (Python 3.4+) uses objects that are more readable and composable. In new code, prefer pathlib. os is still needed for environment variables, process management, file permissions, and low-level OS operations that pathlib does not cover.
Prefer os.scandir() over os.listdir() for performance. os.scandir() returns DirEntry objects which cache stat info — so checking is_file() on an entry doesn't make another system call. For large directories, this is significantly faster.
os.scandir — the efficient directory lister
import os
# os.scandir() is faster than os.listdir() — returns DirEntry objects
# DirEntry has .name, .path, .is_file(), .is_dir(), .stat()
with os.scandir(".") as entries:
for entry in entries:
if entry.is_file():
stat = entry.stat()
print(f"{entry.name}: {stat.st_size} bytes")
# Find all Python files recursively
def find_py_files(root):
for entry in os.scandir(root):
if entry.is_dir(follow_symlinks=False):
yield from find_py_files(entry.path)
elif entry.name.endswith(".py"):
yield entry.path
for py_file in find_py_files("."):
print(py_file)Useful for scripts and servers. os.getpid() gives the current process ID. os.cpu_count() tells you how many CPUs are available (useful when setting ThreadPoolExecutor or ProcessPoolExecutor worker counts). os.uname() gives OS info on Unix systems.
Process and system information
import os
# Current process
print(os.getpid()) # process ID
print(os.getppid()) # parent process ID (Unix)
# User and group (Unix)
print(os.getuid()) # user ID
print(os.getgid()) # group ID
# CPU count
print(os.cpu_count()) # number of CPUs
# File permissions
os.chmod("script.py", 0o755) # rwxr-xr-x
# Symbolic links
os.symlink("target.txt", "link.txt") # create symlink
os.readlink("link.txt") # read symlink target
# Temporary files — prefer tempfile module
import tempfile
with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as f:
f.write(b"temporary data")
print(f.name) # /tmp/tmpXXXXXX.tmpos.path.join() ignores everything before an absolute path component. os.path.join("/home/user", "/etc/config") returns "/etc/config" — the absolute second argument discards the first. This is a common source of path bugs. Validate that dynamic path components are relative before joining.os.system() is not the right way to run commands. It gives you only the exit code, not the output. Use the subprocess module (subprocess.run(), subprocess.check_output()) for anything serious — it gives full control over stdin/stdout/stderr, timeouts, and error handling.os.makedirs() without exist_ok=True raises FileExistsError. Always pass exist_ok=True when the directory may already exist — which is true in almost every real scenario.✅ Intermediate tab complete — check your understanding
- I can use os.walk to process every file in a directory tree recursively
- I know that os.scandir() returns DirEntry objects with cached stat info — faster than os.listdir()
POSIX compliance, inodes, and file metadata
What you'll learn: POSIX standard and platform differences (Windows vs Unix). File metadata via os.stat() — inode numbers, modification times, permissions. os.open() for low-level file descriptor operations.
How to read this tab: Read when working on cross-platform tools or when you need to check file metadata beyond just existence.
POSIX compliance and platform differences
The os module exposes POSIX-defined functions on Unix systems and Win32 equivalents on Windows. Where behaviour differs, os.name is 'posix' on Unix/macOS and 'nt' on Windows. os.sep is '/' on Unix and '\\' on Windows — which is why you should always use os.path.join() or pathlib.Path rather than string concatenation. The os module documentation explicitly notes which functions are only available on certain platforms.
inode, st_mtime, and file metadata
os.stat(path) returns a os.stat_result object. Key fields: st_size (bytes), st_mtime (modification time as Unix timestamp), st_ctime (creation time on Windows; inode change time on Unix — not creation time), st_ino (inode number), st_mode (permission bits — interpret with stat module), st_nlink (hard link count). Note: st_ctime is not "creation time" on Unix — this is a common misconception documented in the Python docs.
✅ Expert tab complete
- I know what an inode is and how to get file metadata with os.stat()
- I understand the key platform differences between Windows and POSIX systems