Copying file contents efficiently is essential for backups, data migration, and file processing. Python provides multiple approaches ranging from simple one-liners to fine-grained control over the copying process.
Copy Files with shutil (Recommended)
The shutil module provides optimized file copying using OS-level system calls for best performance.
import shutil
shutil.copyfile("source.txt", "destination.txt")
shutil.copy2("source.txt", "backup.txt")
print("File copied successfully")Compare shutil Copy Functions
import shutil
import os
source = "document.txt"
shutil.copyfile(source, "copy1.txt")
shutil.copy(source, "./backups/") # Creates backups/document.txt
shutil.copy2(source, "copy2.txt")
original_stat = os.stat(source)
copy2_stat = os.stat("copy2.txt")
print(f"Original modified time: {original_stat.st_mtime}")
print(f"copy2 modified time: {copy2_stat.st_mtime}") # Same| Function | Content | Permissions | Timestamps | Dest Can Be Dir |
|---|---|---|---|---|
copyfile() |
✅ | ❌ | ❌ | ❌ |
copy() |
✅ | ✅ | ❌ | ✅ |
copy2() |
✅ | ✅ | ✅ | ✅ |
Copy Large Files with Chunked Reading
For files larger than available memory, or when you need to process data during copying, read in chunks.
source = "large_video.mp4"
destination = "copy.mp4"
buffer_size = 1024 * 1024
with open(source, 'rb') as f_in, open(destination, 'wb') as f_out:
while chunk := f_in.read(buffer_size):
f_out.write(chunk)
print("Large file copied successfully")Copy with Progress Tracking
import os
def copy_with_progress(source: str, destination: str, buffer_size: int = 1024 * 1024):
"""Copy file with progress percentage display."""
file_size = os.path.getsize(source)
copied = 0
with open(source, 'rb') as f_in, open(destination, 'wb') as f_out:
while chunk := f_in.read(buffer_size):
f_out.write(chunk)
copied += len(chunk)
progress = (copied / file_size) * 100
print(f"\rProgress: {progress:.1f}%", end="")
print("\nCopy complete!")
copy_with_progress("large_file.zip", "backup.zip")Copy Between Open File Objects
Use copyfileobj() when working with already-open file handles or file-like objects.
import shutil
with open('source.txt', 'rb') as src:
with open('destination.txt', 'wb') as dst:
shutil.copyfileobj(src, dst)Specify Buffer Size
import shutil
with open('large_file.bin', 'rb') as src:
with open('copy.bin', 'wb') as dst:
shutil.copyfileobj(src, dst, length=65536)Append File Contents
Merge one file into another by opening in append mode.
import shutil
with open('log_archive.txt', 'a') as archive:
with open('today.log', 'r') as today:
shutil.copyfileobj(today, archive)
print("Log appended to archive")Append Multiple Files
import shutil
from pathlib import Path
log_files = sorted(Path('./logs').glob('*.log'))
with open('combined.log', 'w') as combined:
for log_file in log_files:
combined.write(f"\n=== {log_file.name} ===\n")
with open(log_file, 'r') as f:
shutil.copyfileobj(f, combined)
print(f"Combined {len(log_files)} log files")Copy with Error Handling
Implement robust copying with proper error handling.
import shutil
from pathlib import Path
def safe_copy(source: str, destination: str) -> bool:
"""Copy file with comprehensive error handling."""
source_path = Path(source)
dest_path = Path(destination)
if not source_path.exists():
print(f"Error: Source file '{source}' not found")
return False
dest_path.parent.mkdir(parents=True, exist_ok=True)
try:
shutil.copy2(source, destination)
print(f"Copied: {source} -> {destination}")
return True
except PermissionError:
print(f"Error: Permission denied for '{destination}'")
return False
except shutil.SameFileError:
print(f"Error: Source and destination are the same file")
return False
except OSError as e:
print(f"Error: {e}")
return False
safe_copy("data.txt", "./backup/data.txt")Copy Text Files with Encoding Control
For text files requiring encoding conversion or line ending normalization.
def copy_with_encoding(source: str, destination: str,
source_encoding: str = 'utf-8',
dest_encoding: str = 'utf-8'):
"""Copy text file with encoding conversion."""
with open(source, 'r', encoding=source_encoding) as f_in:
content = f_in.read()
with open(destination, 'w', encoding=dest_encoding) as f_out:
f_out.write(content)
copy_with_encoding('old_file.txt', 'new_file.txt',
source_encoding='latin-1',
dest_encoding='utf-8')Normalize Line Endings
def copy_normalize_newlines(source: str, destination: str):
"""Copy text file, converting all line endings to Unix style."""
with open(source, 'r', newline=None) as f_in: # Universal newline mode
content = f_in.read()
with open(destination, 'w', newline='\n') as f_out:
f_out.write(content)
copy_normalize_newlines('windows_file.txt', 'unix_file.txt')Quick Reference
| Task | Method | Notes |
|---|---|---|
| Fast copy, content only | shutil.copyfile(src, dst) |
Destination must be file path |
| Copy with metadata | shutil.copy2(src, dst) |
Best for backups |
| Copy open file handles | shutil.copyfileobj(f_in, f_out) |
Works with file-like objects |
| Large files with control | Manual chunked reading | Progress tracking, processing |
| Append to file | copyfileobj with 'a' mode |
Merge files |
Conclusion
Use shutil.copy2() for general file copying. It's optimized, handles edge cases, and preserves file metadata. For large files requiring progress tracking or custom processing, implement chunked reading with a buffer. Use copyfileobj() when working with open file handles or file-like objects such as network streams. Always use binary mode ('rb', 'wb') for non-text files to prevent data corruption.