Introduction
Python provides two standard ways to rename files: os.rename() and pathlib.Path.rename(). Both change a file's name or move it to a different directory. os.rename() works with string paths and is available in all Python versions. pathlib.Path.rename() provides an object-oriented interface and is available in Python 3.4+. For moving files across filesystems, use shutil.move() since os.rename() fails when source and destination are on different mount points.
Using os.rename()
1import os
2
3# Rename a file in the same directory
4os.rename('old_name.txt', 'new_name.txt')
5
6# Rename with full paths
7os.rename('/home/user/old_name.txt', '/home/user/new_name.txt')
8
9# Move to a different directory (same filesystem only)
10os.rename('report.txt', 'archive/report.txt')
os.rename() takes two arguments: the current path and the new path. If the destination already exists, behavior varies by OS — on Unix it silently overwrites, on Windows it raises FileExistsError.
Using pathlib.Path.rename()
1from pathlib import Path
2
3# Rename a file
4path = Path('old_name.txt')
5new_path = path.rename('new_name.txt')
6print(new_path) # new_name.txt
7
8# Rename with parent directory
9path = Path('/home/user/old_name.txt')
10path.rename('/home/user/new_name.txt')
11
12# Using the with_name() helper
13path = Path('document.txt')
14new_path = path.rename(path.with_name('document_v2.txt'))
15
16# Change just the extension
17path = Path('data.csv')
18new_path = path.rename(path.with_suffix('.tsv'))
19print(new_path) # data.tsv
Path.rename() returns the new Path object (Python 3.8+). with_name() and with_suffix() create new paths based on the original, making renames more readable.
Using shutil.move()
For moving files across filesystems:
1import shutil
2
3# Works across different filesystems
4shutil.move('report.txt', '/mnt/backup/report.txt')
5
6# Also works for simple renames
7shutil.move('old.txt', 'new.txt')
shutil.move() falls back to copy-then-delete when os.rename() fails, making it work across mount points, network drives, and different partitions.
Batch Renaming
1import os
2from pathlib import Path
3
4# Rename all .txt files to .md
5for path in Path('.').glob('*.txt'):
6 path.rename(path.with_suffix('.md'))
7
8# Add a prefix to all files in a directory
9for path in Path('photos').glob('*.jpg'):
10 new_name = f"vacation_{path.name}"
11 path.rename(path.parent / new_name)
12
13# Sequential numbering
14files = sorted(Path('images').glob('*.png'))
15for i, path in enumerate(files, start=1):
16 new_name = f"image_{i:03d}{path.suffix}"
17 path.rename(path.parent / new_name)
18 # image_001.png, image_002.png, ...
Safe Renaming (Avoid Overwrites)
1from pathlib import Path
2
3def safe_rename(source, destination):
4 """Rename without overwriting existing files."""
5 dest = Path(destination)
6 if dest.exists():
7 raise FileExistsError(f"Destination already exists: {dest}")
8 return Path(source).rename(dest)
9
10# Or generate a unique name
11def rename_with_increment(source, destination):
12 dest = Path(destination)
13 stem = dest.stem
14 suffix = dest.suffix
15 counter = 1
16
17 while dest.exists():
18 dest = dest.parent / f"{stem}_{counter}{suffix}"
19 counter += 1
20
21 return Path(source).rename(dest)
22
23# report.txt → report_1.txt → report_2.txt
24rename_with_increment('report.txt', 'archive/report.txt')
Renaming with Regular Expressions
1import re
2from pathlib import Path
3
4# Replace spaces with underscores in filenames
5for path in Path('.').glob('*'):
6 if ' ' in path.name:
7 new_name = path.name.replace(' ', '_')
8 path.rename(path.parent / new_name)
9
10# Extract and reformat dates in filenames
11# "Report 2025-01-15.pdf" → "2025-01-15_Report.pdf"
12for path in Path('reports').glob('*.pdf'):
13 match = re.search(r'(\d{4}-\d{2}-\d{2})', path.stem)
14 if match:
15 date = match.group(1)
16 name_without_date = path.stem.replace(date, '').strip()
17 new_name = f"{date}_{name_without_date}{path.suffix}"
18 path.rename(path.parent / new_name)
Error Handling
1import os
2from pathlib import Path
3
4def rename_file(old_path, new_path):
5 old = Path(old_path)
6 new = Path(new_path)
7
8 if not old.exists():
9 raise FileNotFoundError(f"Source file not found: {old}")
10
11 if not old.is_file():
12 raise IsADirectoryError(f"Source is not a file: {old}")
13
14 # Create destination directory if needed
15 new.parent.mkdir(parents=True, exist_ok=True)
16
17 try:
18 return old.rename(new)
19 except OSError as e:
20 # Falls back to copy+delete for cross-filesystem moves
21 import shutil
22 shutil.move(str(old), str(new))
23 return new
Common Pitfalls
os.rename() fails across filesystems: On Linux/macOS, moving a file to a different mount point with os.rename() raises OSError: [Errno 18] Invalid cross-device link. Use shutil.move() which handles cross-filesystem moves by copying then deleting.
Silent overwrite on Unix: On Linux/macOS, os.rename() silently overwrites the destination if it exists. On Windows, it raises FileExistsError. Check Path.exists() before renaming if you want consistent cross-platform behavior.
Not creating the destination directory: Renaming to a path in a non-existent directory raises FileNotFoundError. Create the directory first with os.makedirs(dir, exist_ok=True) or Path.mkdir(parents=True, exist_ok=True).
Using relative paths in batch operations: When iterating with glob(), the paths are relative to the glob root. Using path.rename('new_name') places the file in the current working directory, not in the file's original directory. Use path.rename(path.parent / 'new_name') to keep it in the same directory.
Race conditions with existence checks: Checking if not exists() then renaming has a TOCTOU (time-of-check-to-time-of-use) race condition. Another process may create a file with the same name between the check and the rename. For atomic operations, use os.replace() or handle the exception.
Summary
os.rename(old, new) renames or moves files within the same filesystem
Path.rename(new) provides the same functionality with an object-oriented API
Use Path.with_name() and Path.with_suffix() for clean rename expressions
shutil.move() works across filesystems by falling back to copy-then-delete
Check for existing destination files to avoid silent overwrites on Unix
Use Path.parent / new_name in batch operations to keep files in their original directory