Skip to content

How to Change File Extensions in Python with Pathlib

Learn how to safely rename file extensions in Python using Pathlib's with_suffix() and rename() methods for single files or bulk operations.

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

file_path = Path("documents/report.txt")

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")

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

    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


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

    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")

print(file_path.with_suffix(".bz2"))  # archive.tar.bz2

new_path = file_path.with_name(file_path.stem + ".zip")
print(new_path)  # archive.tar.zip

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


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

    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

Legacy Method with os.path

For reference, here's the older approach using os:

import os

filename = "data.txt"

base, ext = os.path.splitext(filename)

new_name = base + ".csv"

os.rename(filename, new_name)

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')

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() 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.
Navigation