Skip to main content

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

TaskPathlibos.path
Change extensionp.with_suffix(".new")splitext() + concatenation
Rename filep.rename(target)os.rename(src, dst)
Find filesp.glob("*.txt")os.listdir() + filtering
Check existsp.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() or rglob() for batch operations on multiple files.
  • Always check for existing files to avoid accidental overwrites.
  • Prefer pathlib over os.path for cleaner, cross-platform code.