Skip to content

Latest commit

 

History

History
192 lines (139 loc) · 7.24 KB

File metadata and controls

192 lines (139 loc) · 7.24 KB

Working with directories

Most Python code that deals with files and folders should nowadays use the pathlib module instead of the older os and os.path functions.

pathlib offers:

  • Cleaner, more readable code
  • Platform-independent path handling (/ works on Windows too)
  • Object-oriented interface
  • Chainable operations
  • Better exception messages

:::{tip} Use pathlib for all new code. Only use os when you specifically need low-level operations (os.scandir, os.walk, os.chmod, process handling, etc.) or when supporting very old Python versions (< 3.4). :::

Why use pathlib?

  • Object-Oriented: Paths are objects, not just strings, so you can call methods directly on them.

  • Readable: The / operator for joining paths is much cleaner than nested os.path.join calls.

  • Cross-platform: It handles the difference between Windows () and Unix (/) separators automatically.

Importing pathlib

from pathlib import Path

You usually only need this one import for most file-system tasks.

Creating Path objects

# Common useful paths

home       = Path.home()                     
cwd        = Path.cwd()                      # current working directory
desktop    = Path.home() / "Desktop"
document   = Path.home() / "Documents"
config     = Path.home() / ".config" / "myapp" / "settings.toml"
project    = Path("/home/maikel/projects/my-awesome-app")
readme     = project / "README.md"

Listing files in a directory

folder = Path.home() / "projects"

# All entries (files + folders)
for item in folder.iterdir():
    print(item.name)

# Only file names as list
files = [p.name for p in folder.iterdir()]
print(files)

# Only Python files
py_files = [p.name for p in folder.glob("*.py")]
print(py_files)

# Recursively find all .md files
markdowns = list(folder.rglob("*.md"))

Changing the current working directory

pathlib does not have a method to change the current directory — you still use os for that:

import os

os.chdir(Path.home() / "projects")
# or
os.chdir(str(Path.home() / "projects"))   # sometimes needed for older code

But you can easily get & show the current directory as a Path:

print(Path.cwd())

Checking existence, file vs directory

p = Path("notes.txt")

print(p.exists())       # True / False
print(p.is_file())      # True if regular file
print(p.is_dir())       # True if directory
print(p.is_symlink())   # True if symbolic link

File size and modification time

from datetime import datetime

photo = Path("vacation-2025.jpg")

# File size in bytes
size = photo.stat().st_size
print(f"Size: {size:,} bytes")

# Last modification time
mtime = datetime.fromtimestamp(photo.stat().st_mtime)
print(mtime.strftime("%Y-%m-%d %H:%M:%S"))

Creating, deleting, renaming

# Create directories (like mkdir -p)
report_folder = Path.home() / "Reports" / "2026" / "Q1"
report_folder.mkdir(parents=True, exist_ok=True)

# Create an empty file
(Path("temp.txt")).touch()

# Delete file
(Path("old.log")).unlink(missing_ok=True)

# Delete directory (must be empty)
(Path("empty_folder")).rmdir()

# Rename / move
source = Path("draft.md")
source.rename(source.with_name("final.md"))
# or move to different folder
source.rename(Path.home() / "Archive" / source.name)

Building paths cleanly

# Very readable and platform-safe
logfile = (
    Path.home()
    / ".local"
    / "share"
    / "myapp"
    / "logs"
    / f"app-{datetime.now():%Y%m%d}.log"
)

# Alternative style
config = Path("config") / "prod" / "database.ini"

Quick reference table – old vs new

Task Old (os / os.path) Modern (pathlib) Notes
Current working directory os.getcwd() Path.cwd()
User home directory os.path.expanduser("~") Path.home() Platform-independent
Join path components os.path.join(a, b, c) Path(a) / b / c or Path(a, b, c) / operator is very readable
Base filename os.path.basename(p) Path(p).name Includes extension
Directory part os.path.dirname(p) Path(p).parent Returns a Path object
Exists (file or dir) os.path.exists(p) Path(p).exists()
Is regular file? os.path.isfile(p) Path(p).is_file() Does not follow symlinks
Is directory? os.path.isdir(p) Path(p).is_dir() Does not follow symlinks
File size (bytes) os.path.getsize(p) Path(p).stat().st_size Requires .stat()
Last modification time os.path.getmtime(p) Path(p).stat().st_mtime Unix timestamp (float)
List directory contents os.listdir(path) [p.name for p in Path(path).iterdir()] iterdir() → Path objects
Pattern matching (glob) glob.glob("*.py") Path().glob("*.py") Returns iterator of Path objects
Recursive glob Path().rglob("*.py") Like **/*.py in shell
Delete / remove file os.remove(p) Path(p).unlink(missing_ok=True) missing_ok prevents exceptions
Create directory tree os.makedirs(path, exist_ok=True) Path(path).mkdir(parents=True, exist_ok=True) parents=Truemkdir -p

Examples

# Find largest file in folder
biggest = max(folder.iterdir(), key=lambda p: p.stat().st_size if p.is_file() else 0)
print(f"Largest file: {biggest.name}{biggest.stat().st_size:,} bytes")

# Backup all .py files
for py in folder.rglob("*.py"):
    backup = py.with_suffix(".py.bak")
    py.replace(backup)           # atomic move/rename

# Create dated report folder if needed
today = datetime.now().strftime("%Y-%m-%d")
(Path("reports") / today).mkdir(exist_ok=True)