Recently, I was working on a data migration project where I needed to copy thousands of customer invoices from one directory to another while renaming them with a standardized format.
The challenge was that the original files had inconsistent naming conventions, and I needed to organize them systematically. This is a common scenario in Python development, and I’ll show you exactly how to handle it.
In this article, I’ll cover multiple methods to copy files and rename them simultaneously in Python. We’ll explore practical examples that you can use in your projects.
File Operations in Python
Before we start copying and renaming files, it’s important to understand the different modules Python offers for file operations.
Python provides several built-in modules for file manipulation: os, shutil, and pathlib. Each has its strengths depending on your specific needs.
The shutil module is particularly useful for high-level file operations, while os provides lower-level system interface functions.
Method 1: Use shutil.copy() with os.rename()
This is the simple approach that I frequently use in my projects. We first copy the file to the destination, then rename it.
Let me show you how this works with a practical example:
import shutil
import os
def copy_and_rename_basic(source_file, destination_dir, new_name):
"""
Copy a file to destination directory and rename it
Args:
source_file (str): Path to the source file
destination_dir (str): Destination directory path
new_name (str): New name for the file
"""
try:
# Ensure destination directory exists
os.makedirs(destination_dir, exist_ok=True)
# Copy file to destination
temp_path = shutil.copy(source_file, destination_dir)
# Create new file path with desired name
new_path = os.path.join(destination_dir, new_name)
# Rename the copied file
os.rename(temp_path, new_path)
print(f"Successfully copied and renamed: {source_file} -> {new_path}")
return new_path
except FileNotFoundError:
print(f"Error: Source file {source_file} not found")
except PermissionError:
print(f"Error: Permission denied when copying {source_file}")
except Exception as e:
print(f"Unexpected error: {e}")
# Example usage
source_file = "C:/Users/Documents/report_2024_jan.pdf"
destination_dir = "C:/Users/Documents/organized_reports"
new_name = "january_2024_financial_report.pdf"
copy_and_rename_basic(source_file, destination_dir, new_name)You can see the output in the screenshot below.

This method works well for single-file operations. However, there’s a more elegant approach using direct path manipulation.
Here’s an improved version that combines the copy and rename in one step:
import shutil
import os
def copy_with_new_name(source_file, destination_path):
"""
Copy file directly to new location with new name
Args:
source_file (str): Path to source file
destination_path (str): Full path including new filename
"""
try:
# Ensure destination directory exists
destination_dir = os.path.dirname(destination_path)
os.makedirs(destination_dir, exist_ok=True)
# Copy file directly with new name
shutil.copy(source_file, destination_path)
print(f"File copied and renamed: {destination_path}")
return destination_path
except Exception as e:
print(f"Error copying file: {e}")
return None
# Example: Copy employee handbook with new name
source = "C:/Company/Documents/handbook_v1.pdf"
destination = "C:/Company/Employee_Resources/employee_handbook_2024.pdf"
copy_with_new_name(source, destination)Method 2: Use shutil.copy2() with Enhanced Metadata Preservation
When working with important files, I often need to preserve metadata like timestamps and file permissions. The shutil.copy2() function is perfect for this.
This method maintains the original file’s metadata while allowing us to rename it:
import shutil
import os
from datetime import datetime
def copy_and_rename_with_metadata(source_file, destination_dir, new_name):
"""
Copy file with metadata preservation and rename
Args:
source_file (str): Source file path
destination_dir (str): Destination directory
new_name (str): New filename
"""
try:
# Create destination directory if it doesn't exist
os.makedirs(destination_dir, exist_ok=True)
# Full destination path
destination_path = os.path.join(destination_dir, new_name)
# Copy with metadata preservation
shutil.copy2(source_file, destination_path)
# Get file statistics to verify metadata preservation
src_stat = os.stat(source_file)
dst_stat = os.stat(destination_path)
print(f"File copied and renamed successfully!")
print(f"Source: {source_file}")
print(f"Destination: {destination_path}")
print(f"Original modified time: {datetime.fromtimestamp(src_stat.st_mtime)}")
print(f"New file modified time: {datetime.fromtimestamp(dst_stat.st_mtime)}")
return destination_path
except Exception as e:
print(f"Error during copy operation: {e}")
return None
# Example: Backup important tax documents
source_file = "C:/Users/Documents/TaxReturns/return_2023.pdf"
backup_dir = "C:/Users/Documents/TaxBackups/2024"
new_name = "tax_return_2023_backup.pdf"
copy_and_rename_with_metadata(source_file, backup_dir, new_name)You can see the output in the screenshot below.

For scenarios where you need to copy multiple files with pattern-based renaming:
import shutil
import os
import glob
from datetime import datetime
def batch_copy_and_rename(source_pattern, destination_dir, name_template):
"""
Copy multiple files matching pattern and rename them
Args:
source_pattern (str): Glob pattern for source files
destination_dir (str): Destination directory
name_template (str): Template for new names (use {index} for numbering)
"""
try:
# Find all files matching pattern
source_files = glob.glob(source_pattern)
if not source_files:
print("No files found matching the pattern")
return []
# Create destination directory
os.makedirs(destination_dir, exist_ok=True)
copied_files = []
for index, source_file in enumerate(source_files, 1):
# Extract file extension
_, ext = os.path.splitext(source_file)
# Generate new filename
new_name = name_template.format(index=index) + ext
destination_path = os.path.join(destination_dir, new_name)
# Copy with metadata
shutil.copy2(source_file, destination_path)
copied_files.append(destination_path)
print(f"Copied: {os.path.basename(source_file)} -> {new_name}")
print(f"Successfully copied {len(copied_files)} files")
return copied_files
except Exception as e:
print(f"Error in batch operation: {e}")
return []
# Example: Copy all invoice PDFs and rename them systematically
source_pattern = "C:/Business/Invoices/*.pdf"
destination_dir = "C:/Business/Organized_Invoices"
name_template = "invoice_{index:03d}" # Creates invoice_001, invoice_002, etc.
batch_copy_and_rename(source_pattern, destination_dir, name_template)Method 3: Use pathlib for Modern Python Development
The pathlib module, introduced in Python 3.4, provides an object-oriented approach to file system operations. I find it more intuitive and readable.
Here’s how to copy and rename files using pathlib:
from pathlib import Path
import shutil
def copy_and_rename_pathlib(source_path, destination_dir, new_name):
"""
Copy and rename files using pathlib (Python 3.4+)
Args:
source_path (str or Path): Source file path
destination_dir (str or Path): Destination directory
new_name (str): New filename
"""
try:
# Convert to Path objects
source = Path(source_path)
dest_dir = Path(destination_dir)
# Check if source file exists
if not source.exists():
print(f"Source file does not exist: {source}")
return None
# Create destination directory if needed
dest_dir.mkdir(parents=True, exist_ok=True)
# Create full destination path
destination = dest_dir / new_name
# Copy file
shutil.copy2(str(source), str(destination))
print(f"File copied successfully!")
print(f"From: {source}")
print(f"To: {destination}")
return destination
except Exception as e:
print(f"Error using pathlib method: {e}")
return None
# Example: Copy project documentation
source_path = Path("C:/Projects/MyApp/README.md")
destination_dir = Path("C:/Projects/Archive/MyApp_v1.0")
new_name = "project_documentation.md"
copy_and_rename_pathlib(source_path, destination_dir, new_name)
You can see the output in the screenshot below.

For more advanced pathlib operations, here’s a function that handles multiple file extensions and provides detailed feedback:
from pathlib import Path
import shutil
from datetime import datetime
class FileCopyRename:
"""Class to handle file copy and rename operations using pathlib"""
def __init__(self, source_dir, destination_dir):
self.source_dir = Path(source_dir)
self.destination_dir = Path(destination_dir)
self.destination_dir.mkdir(parents=True, exist_ok=True)
def copy_with_timestamp(self, filename, new_base_name=None):
"""
Copy file and add timestamp to name
Args:
filename (str): Original filename
new_base_name (str): New base name (optional)
"""
try:
source_file = self.source_dir / filename
if not source_file.exists():
print(f"File not found: {source_file}")
return None
# Generate timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Determine new filename
if new_base_name:
new_name = f"{new_base_name}_{timestamp}{source_file.suffix}"
else:
new_name = f"{source_file.stem}_{timestamp}{source_file.suffix}"
destination_file = self.destination_dir / new_name
# Copy file
shutil.copy2(str(source_file), str(destination_file))
print(f"File copied with timestamp: {new_name}")
return destination_file
except Exception as e:
print(f"Error copying file with timestamp: {e}")
return None
def copy_multiple_files(self, file_pattern, name_prefix="file"):
"""
Copy multiple files matching a pattern
Args:
file_pattern (str): Glob pattern to match files
name_prefix (str): Prefix for renamed files
"""
try:
matching_files = list(self.source_dir.glob(file_pattern))
if not matching_files:
print(f"No files found matching pattern: {file_pattern}")
return []
copied_files = []
for index, source_file in enumerate(matching_files, 1):
# Create new filename with index
new_name = f"{name_prefix}_{index:03d}{source_file.suffix}"
destination_file = self.destination_dir / new_name
# Copy file
shutil.copy2(str(source_file), str(destination_file))
copied_files.append(destination_file)
print(f"Copied: {source_file.name} -> {new_name}")
print(f"Successfully copied {len(copied_files)} files")
return copied_files
except Exception as e:
print(f"Error in bulk copy operation: {e}")
return []
# Example: Copy customer data files with organized naming
file_manager = FileCopyRename(
source_dir="C:/Business/CustomerData/Raw",
destination_dir="C:/Business/CustomerData/Processed"
)
# Copy single file with timestamp
file_manager.copy_with_timestamp("customer_survey.xlsx", "survey_results")
# Copy multiple CSV files with systematic naming
file_manager.copy_multiple_files("*.csv", "customer_data")The pathlib approach offers several advantages over traditional string-based path operations. It’s more readable, handles different operating systems automatically, and provides better error handling.
I particularly like using pathlib when working with complex directory structures because the / operator makes path joining much cleaner than os.path.join().
You can also read:
- Send Emails Using Python
- Access Modifiers in Python
- Fastest Sorting Algorithm in Python
- raw_input Function in Python for User Input

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.