How to Change File Extensions in Python with Pathlib
Renaming data.txt to data.csv is a one-line operation with Python's modern pathlib module. It handles path manipulation cleanly without string concatenation.
Using pathlib.Path.with_suffix()
The pathlib module treats file paths as objects rather than strings, providing clean methods for common operations:
from pathlib import Path
# Create a Path object
file_path = Path("documents/report.txt")
# Create new path with different extension
new_path = file_path.with_suffix(".md")
print(f"Original: {file_path}")
print(f"New path: {new_path}")
Output:
Original: documents/report.txt
New path: documents/report.md
Actually Renaming the File
The with_suffix() method returns a new Path object and it doesn't rename the file. Use rename() to perform the actual operation:
from pathlib import Path
file_path = Path("data.txt")
# Check if file exists before renaming
if file_path.exists():
new_path = file_path.with_suffix(".csv")
file_path.rename(new_path)
print(f"Renamed to: {new_path}")
else:
print(f"File not found: {file_path}")
Bulk Renaming Files
Change all files with one extension to another:
from pathlib import Path
def change_extensions(folder: str, old_ext: str, new_ext: str) -> int:
"""
Change all files from old_ext to new_ext in the specified folder.
Returns the number of files renamed.
"""
folder_path = Path(folder)
count = 0
# Ensure extensions have dots
if not old_ext.startswith('.'):
old_ext = f'.{old_ext}'
if not new_ext.startswith('.'):
new_ext = f'.{new_ext}'
for file in folder_path.glob(f"*{old_ext}"):
new_name = file.with_suffix(new_ext)
file.rename(new_name)
print(f"Renamed: {file.name} → {new_name.name}")
count += 1
return count
# Change all .jpeg files to .jpg
renamed = change_extensions("./images", "jpeg", "jpg")
print(f"\nTotal files renamed: {renamed}")
Recursive Renaming
To include subdirectories, use rglob() instead of glob():
from pathlib import Path
def change_extensions_recursive(folder: str, old_ext: str, new_ext: str) -> int:
"""Recursively change extensions in folder and all subdirectories."""
folder_path = Path(folder)
count = 0
# rglob searches recursively
for file in folder_path.rglob(f"*.{old_ext}"):
new_name = file.with_suffix(f".{new_ext}")
file.rename(new_name)
print(f"Renamed: {file.relative_to(folder_path)}")
count += 1
return count
renamed = change_extensions_recursive("./project", "txt", "md")
print(f"Total: {renamed} files renamed")
Handling Files with Multiple Dots
Files like data.backup.txt need special handling since with_suffix() only replaces the last suffix:
from pathlib import Path
file_path = Path("archive.tar.gz")
# with_suffix only changes last extension
print(file_path.with_suffix(".bz2")) # archive.tar.bz2
# To change full extension, use stem and rebuild
new_path = file_path.with_name(file_path.stem + ".zip")
print(new_path) # archive.tar.zip
# To get just the base name without any extensions
base_name = file_path.name.split('.')[0]
new_path = file_path.with_name(f"{base_name}.zip")
print(new_path) # archive.zip
Safe Renaming with Conflict Detection
Avoid overwriting existing files:
from pathlib import Path
def safe_rename(file_path: Path, new_suffix: str) -> Path | None:
"""Rename file safely avoiding overwrites."""
new_path = file_path.with_suffix(new_suffix)
if new_path.exists():
print(f"Warning: {new_path} already exists, skipping")
return None
file_path.rename(new_path)
return new_path
# Usage
original = Path("document.txt")
result = safe_rename(original, ".md")
if result:
print(f"Successfully renamed to {result}")
Adding Numeric Suffix for Conflicts
from pathlib import Path
def rename_with_increment(file_path: Path, new_suffix: str) -> Path:
"""Rename file, adding number if target exists."""
new_path = file_path.with_suffix(new_suffix)
if not new_path.exists():
file_path.rename(new_path)
return new_path
# Find available name with number
counter = 1
stem = new_path.stem
parent = new_path.parent
while True:
numbered_path = parent / f"{stem}_{counter}{new_suffix}"
if not numbered_path.exists():
file_path.rename(numbered_path)
return numbered_path
counter += 1
# document.txt → document.md (or document_1.md if exists)
Legacy Method with os.path
For reference, here's the older approach using os:
import os
filename = "data.txt"
# Split into base and extension
base, ext = os.path.splitext(filename)
# Create new filename
new_name = base + ".csv"
# Rename the file
os.rename(filename, new_name)
Use Pathlib
The pathlib approach is preferred because:
- Cross-platform (handles
/and\automatically) - Object-oriented with chainable methods
- More readable and less error-prone
- Built-in existence checks and file operations
Common Path Operations Reference
from pathlib import Path
p = Path("folder/subfolder/document.txt")
print(p.name) # document.txt
print(p.stem) # document
print(p.suffix) # .txt
print(p.parent) # folder/subfolder
print(p.parts) # ('folder', 'subfolder', 'document.txt')
# Create new paths
print(p.with_name("other.csv")) # folder/subfolder/other.csv
print(p.with_stem("report")) # folder/subfolder/report.txt (Python 3.9+)
print(p.with_suffix(".md")) # folder/subfolder/document.md
Method Comparison
| Task | Pathlib | os.path |
|---|---|---|
| Change extension | p.with_suffix(".new") | splitext() + concatenation |
| Rename file | p.rename(target) | os.rename(src, dst) |
| Find files | p.glob("*.txt") | os.listdir() + filtering |
| Check exists | p.exists() | os.path.exists(p) |
Summary
- Use
Path.with_suffix()to create a new path with a different extension. - Use
Path.rename()to actually rename the file on disk. - Use
glob()orrglob()for batch operations on multiple files. - Always check for existing files to avoid accidental overwrites.
- Prefer
pathliboveros.pathfor cleaner, cross-platform code.