If you want to learn how to python copy file from one directory to another, this guide will walk you through several reliable methods, from using the built‑in shutil module to leveraging the os module and even handling large files efficiently. Whether you are a beginner or an experienced developer, understanding these techniques will help you automate file management tasks and improve your Python workflows.
Introduction
Copying files between directories is a common operation in many Python scripts. Whether you are backing up data, organizing project assets, or preparing inputs for data processing pipelines, being able to move or duplicate files quickly and safely is essential. Python provides several standard‑library modules that abstract the low‑level details of file I/O, allowing you to focus on the logic of your application rather than the mechanics of reading and writing bytes.
The core modules you will use most often are os, shutil, and pathlib. Each has its own strengths and use cases, and modern Python code often blends them for clarity and robustness. In this article we will explore:
- The basic copy operations using shutil.copy and shutil.copy2.
- How to preserve file metadata and why shutil.copy2 is often preferred.
- Using shutil.copyfile for raw byte‑level copying.
- Leveraging os.rename for fast moves within the same filesystem.
- The modern pathlib interface, which makes path manipulation more readable.
- Strategies for copying large files in chunks to reduce memory usage.
- Error handling and best practices to avoid common pitfalls.
- Frequently asked questions about permissions, overwriting, symbolic links, and cross‑platform compatibility.
By the end of this guide you will have a toolbox of techniques you can apply to any file‑copying scenario, ensuring your scripts are both efficient and reliable.
Basic Copy Operations
Using shutil.copy
The shutil module is the go‑to choice for high‑level file operations. The simplest function, shutil.copy(src, dst), copies the content and permission mode of src to dst, but it does not preserve extra metadata such as timestamps or extended attributes. It works for both files and directories (when dst is a directory) Worth knowing..
import shutil
import os
src_path = r"C:\source\example.txt"
dst_dir = r"D:\backup"
# Copy a single file
shutil.copy(src_path, dst_dir)
# Result: D:\backup\example.txt
If dst already exists, shutil.Also, copy will overwrite it without warning. For safety, you can check existence first or use a custom naming scheme.
Using shutil.copy2
When you need to keep the original file's metadata (modification time, creation time, and other attributes), use shutil.copy2. This is especially useful for scripts that depend on timestamps for versioning or logging.
shutil.copy2(src_path, dst_dir)
The function behaves like copy but internally calls shutil.copystat to replicate the source’s stat information. In practice, copy2 is the recommended default for most use cases.
Using shutil.copyfile
If you only care about the raw content and want to avoid copying permission bits, use shutil.copyfile(src, dst). It opens both files in binary mode and writes the data directly, which can be marginally faster when metadata preservation is unnecessary.
shutil.copyfile(src_path, dst_path) # dst_path must be a full file path
Note that copyfile does not copy the file’s mode; you must set permissions manually if needed Most people skip this — try not to. No workaround needed..
Directory‑Level Copying
shutil.copytree
When the destination is a whole directory tree, shutil.Now, copytree(src_dir, dst_dir, dirs_exist_ok=True) will recursively copy all files and subdirectories. The dirs_exist_ok parameter (Python 3.8+) prevents an error if dst_dir already exists, merging the contents instead of aborting.
src_dir = r"C:\source\project"
dst_dir = r"D:\archive\project_backup"
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
If you need to clean the destination before copying, you can add a pre‑step that removes the target directory safely Most people skip this — try not to. No workaround needed..
Fast Moves with os.rename
If the source and destination are on the same filesystem, you can achieve a move (copy + delete) with os.Day to day, rename(src, dst). This operation is atomic, fast, and does not require reading the file content. It is ideal for reorganizing files within a single disk volume.
os.rename(src_path, dst_path)
When you need a copy‑and‑keep behavior, combine os.rename with a temporary location or duplicate the file first Practical, not theoretical..
Modern Path Handling with pathlib
Introduced in Python 3.4 and stabilized in 3.6, the pathlib module provides an object‑oriented interface to filesystem paths. It can simplify copy operations and improve code readability.
from pathlib import Path
src = Path(r"C:\source\example.txt")
dst = Path(r"D:\backup") / src.name
# Using shutil.copy with Path objects (automatically converted to strings)
shutil.copy(src, dst)
You can also chain methods for more complex manipulations, such as creating parent directories on the fly:
(dst.parent).mkdir(parents=True, exist_ok=True)
shutil.copy(src, dst)
Copying Large Files in Chunks
When dealing with large files (hundreds of megabytes or gigabytes), reading the entire content into memory can be inefficient. The shutil module internally uses buffered I/O, but you can still control the buffer size for optimal performance That's the part that actually makes a difference. Simple as that..
def copy_large_file(src, dst, chunk_size=1024*1024): # 1 MiB chunks
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
while
```python
def copy_large_file(src, dst, chunk_size=1024*1024): # 1 MiB chunks
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
while True:
data = fsrc.read(chunk_size)
if not data:
break
fdst.write(data)
Why Chunked Copying Helps
Reading a file in modest‑sized blocks keeps the process’s memory footprint low, which is especially valuable on systems with limited RAM or when copying many large files concurrently. The operating system’s page cache will still prefetch ahead, so I/O throughput remains close to the hardware limit while avoiding the occasional spike that a single‑read‑all approach can cause.
Leveraging shutil.copyfileobj
The standard library already offers a battle‑tested helper that does exactly what the loop above implements:
shutil.copyfileobj(open(src, 'rb'), open(dst, 'wb'), length=chunk_size)
copyfileobj accepts any file‑like objects, making it easy to plug in compressed streams, network sockets, or io.BytesIO buffers without rewriting the core logic.
Adding Progress Feedback
For long‑running copies, a visual progress indicator improves user experience. Pairing tqdm with a custom read wrapper yields a concise solution:
from tqdm import tqdm
def copy_with_progress(src, dst, chunk_size=1024*1024):
total_size = os.read(chunk_size)
if not data:
break
fdst.path.basename(src)) as pbar:
while True:
data = fsrc.getsize(src)
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst, \
tqdm(total=total_size, unit='B', unit_scale=True, desc=os.Think about it: path. write(data)
pbar.
The wrapper updates the progress bar after each chunk, giving an accurate estimate of remaining time and throughput.
### Handling Symlinks and Special Files
When copying directory trees, you may want to preserve symbolic links rather than dereferencing them. `shutil.copytree` supports this via the `symlinks` flag:
```python
shutil.copytree(src_dir, dst_dir, symlinks=True, dirs_exist_ok=True)
Setting symlinks=True recreates the link at the destination pointing to the same target path; if the target lies outside the copied tree, the link remains functional only if the target is accessible from the destination location.
Error‑Resilient Copies
Network or removable media can produce transient I/O errors. Wrapping the copy in a retry loop with exponential backoff makes the operation more dependable:
import time
def resilient_copy(src, dst, retries=3, delay=1):
for attempt in range(1, retries + 1):
try:
shutil.copy2(src, dst)
return
except OSError as exc:
if attempt == retries:
raise
time.sleep(delay * attempt) # back off
Putting It All Together – A Utility Function
Below is a compact utility that combines chunked copying, optional progress reporting, symlink handling, and basic error resilience:
def smart_copy(src, dst, *, chunk_size=1024*1024, show_progress=False,
preserve_symlinks=False, max_retries=2):
if os.path.isdir(src):
shutil.copytree(
src, dst,
copy_function=lambda s, d: _chunked_copy(s, d, chunk_size, show_progress),
symlinks=preserve_symlinks,
dirs_exist_ok=True
)
else:
_chunked_copy(src, dst, chunk_size, show_progress)
def _chunked_copy(src, dst, chunk_size, show_progress):
if show_progress:
total = os.And basename(src)) as pbar:
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
while True:
data = fsrc. write(data)
pbar.Day to day, path. That said, path. read(chunk_size)
if not data:
break
fdst.On top of that, getsize(src)
with tqdm(total=total, unit='B', unit_scale=True,
desc=os. On the flip side, update(len(data))
else:
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
while True:
data = fsrc. read(chunk_size)
if not data:
break
fdst.
### Conclusion
Python’s standard library already provides powerful, high‑level primitives for copying
files, yet understanding the underlying mechanics allows you to tailor behavior to specific needs. By leveraging chunked I/O, metadata preservation, symbolic link handling, and retry logic, you can build strong file-copying utilities that perform well across diverse environments—from local filesystems to network-mounted storage. The key is to balance simplicity with control, using built-in functions like `shutil.copy2` and `shutil.copytree` as starting points, then extending them with custom logic when necessary. With careful attention to buffer sizes, progress tracking, and error resilience, even mundane tasks like copying files can be transformed into reliable, efficient operations suitable for production systems.