os and sys
Work with operating-system services and the running Python interpreter.
os exposes operating-system facilities; sys exposes the current Python process. Prefer
high-level modules such as pathlib, subprocess, and shutil when they express the operation
directly, then use os for lower-level details.
Environment and process state
import os
import sys
debug = os.environ.get("APP_DEBUG", "").casefold() in {"1", "true", "yes"}
data_dir = os.environ.get("APP_DATA_DIR")
if data_dir is None:
print("APP_DATA_DIR is required", file=sys.stderr)
raise SystemExit(2)
print(f"pid={os.getpid()} debug={debug}")os.environ is a mutable mapping of strings. A change affects the current process and future
child processes, not the parent shell. Do not log the entire mapping: it commonly contains
credentials. Use os.getenv() for a single optional value and explicit validation for required
configuration.
The current working directory is process-global:
from pathlib import Path
print(Path.cwd())Avoid os.chdir() in libraries and servers because unrelated code observes the change. Resolve
paths from a known base directory instead.
Command-line arguments and exit status
sys.argv[0] names the script; later entries are raw strings. argparse is usually better than
manual parsing.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("source")
parser.add_argument("--limit", type=int, default=100)
args = parser.parse_args()Return status 0 for success and a nonzero status for failure:
def main() -> int:
return 0
if __name__ == "__main__":
raise SystemExit(main())This pattern is testable and lets Python flush streams and run cleanup handlers. os._exit()
terminates immediately and is only appropriate in specialized post-fork situations.
Standard streams
sys.stdin, sys.stdout, and sys.stderr are text streams. Diagnostics belong on stderr so
stdout can remain machine-readable.
import json
import sys
json.dump({"status": "ok"}, sys.stdout)
sys.stdout.write("\n")
print("processed one item", file=sys.stderr)For binary data, use sys.stdin.buffer and sys.stdout.buffer. Never mix arbitrary text and
binary writes without controlling buffering and encoding.
Filesystem-level operations
Useful os APIs include:
os.scandir()for efficient directory entries and metadata;os.walk()for recursive traversal;os.stat()for metadata;os.replace()for atomic replacement on the same filesystem;os.fspath()to accept both path objects and strings;os.cpu_count()as a hint, not a workload-tuning guarantee.
import os
from pathlib import Path
def regular_files(directory: Path):
with os.scandir(directory) as entries:
for entry in entries:
if entry.is_file(follow_symlinks=False):
yield Path(entry.path)Whether to follow symbolic links is a security decision. Code that scans untrusted trees should usually avoid following them and must handle files disappearing between inspection and use.
Platform portability
Use os.name, sys.platform, and sys.version_info only when behavior genuinely differs.
Prefer capability checks over broad platform branches.
import os
if hasattr(os, "getuid"):
print(os.getuid())Do not split paths on / or construct shell commands with string concatenation. Use pathlib
for paths and subprocess.run([...], shell=False) for external programs.
Common mistakes
- Treating environment variables as trusted or typed values.
- Assuming the working directory is the script directory.
- Catching
OSErrorbroadly and discarding useful details such asfilenameanderrno. - Mutating process-global state in reusable functions.
- Using
sys.exit()deep inside library code instead of raising a domain exception.
Practice
Write a CLI that requires REPORT_DIR, accepts --verbose, prints JSON to stdout, sends progress
to stderr, and returns status 2 for configuration errors. Test it with a missing environment
variable and a path containing spaces.
LEARNING RECORD
Finish this lesson
Mark it complete when you can explain the main decision without looking.
KNOWLEDGE CHECK