Python
file management
file manipulation
programming tutorial
shutil module

How do I move a file in Python?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Moving files is a routine operation in Python scripts, from log rotation to ETL pipelines and deployment tooling. The code is simple, but production behavior depends on filesystem boundaries, overwrite rules, and error handling. The safest default is to use shutil.move with explicit checks and clear failure paths.

Choose the Right API

Python gives you two common options:

  • os.rename for simple same-filesystem renames or moves.
  • shutil.move for general-purpose moves, including cross-filesystem cases.

os.rename is fast and direct, but it can fail when source and destination are on different mounts. shutil.move handles that by falling back to copy-then-delete behavior when needed.

Basic Move With shutil.move

Use pathlib for readable path code and shutil.move for the operation.

python
1from pathlib import Path
2import shutil
3
4src = Path("data/incoming/report.csv")
5dst = Path("data/archive/report.csv")
6
7dst.parent.mkdir(parents=True, exist_ok=True)
8shutil.move(str(src), str(dst))
9
10print(f"moved {src} -> {dst}")

This covers most script needs and works on Linux, macOS, and Windows.

Preserve Safety With Pre-Checks

Moving files in automation should include checks for existence and destination collisions.

python
1from pathlib import Path
2import shutil
3
4
5def safe_move(src: Path, dst: Path, overwrite: bool = False) -> None:
6    if not src.exists():
7        raise FileNotFoundError(f"source does not exist: {src}")
8
9    if dst.exists() and not overwrite:
10        raise FileExistsError(f"destination already exists: {dst}")
11
12    dst.parent.mkdir(parents=True, exist_ok=True)
13
14    if dst.exists() and overwrite:
15        if dst.is_dir():
16            raise IsADirectoryError(f"destination is directory: {dst}")
17        dst.unlink()
18
19    shutil.move(str(src), str(dst))
20
21
22safe_move(Path("tmp/a.txt"), Path("tmp/archive/a.txt"), overwrite=True)

This pattern avoids silent clobbering and makes behavior explicit.

Moving Into a Directory

If destination path is a directory, shutil.move keeps the original filename.

python
1import shutil
2from pathlib import Path
3
4src = Path("exports/january.json")
5dst_dir = Path("exports/backup")
6
7dst_dir.mkdir(parents=True, exist_ok=True)
8result_path = shutil.move(str(src), str(dst_dir))
9
10print(result_path)  # exports/backup/january.json

This is useful for bulk archival jobs where filename stays unchanged.

Cross-Filesystem Behavior

On same filesystem, move can be a metadata rename operation. Across filesystems, Python typically copies bytes then removes source file. That can be slower and can fail midway due to permissions or disk space.

If atomic behavior is required, keep source and destination on same filesystem and use rename semantics. For critical workloads, log each move and verify destination hash before deleting source when implementing custom copy workflows.

Error Handling You Actually Need

Catch expected exceptions and include contextual logs.

python
1from pathlib import Path
2import shutil
3
4
5def move_with_logging(src: Path, dst: Path) -> bool:
6    try:
7        dst.parent.mkdir(parents=True, exist_ok=True)
8        shutil.move(str(src), str(dst))
9        print(f"SUCCESS move {src} -> {dst}")
10        return True
11    except FileNotFoundError:
12        print(f"ERROR source missing: {src}")
13    except PermissionError:
14        print(f"ERROR permission denied for {src} or {dst}")
15    except OSError as exc:
16        print(f"ERROR move failed: {exc}")
17
18    return False

In data pipelines, return status values and aggregate failures for retry instead of crashing the first time.

Batch Moves

For many files, iterate with deterministic filtering and explicit destination mapping.

python
1from pathlib import Path
2import shutil
3
4source_dir = Path("incoming")
5archive_dir = Path("archive")
6archive_dir.mkdir(exist_ok=True)
7
8for file_path in source_dir.glob("*.log"):
9    target = archive_dir / file_path.name
10    shutil.move(str(file_path), str(target))
11    print(f"archived {file_path.name}")

Keep naming collisions in mind for batch jobs that run repeatedly.

Common Pitfalls

A common pitfall is using os.rename assuming it always works across disks, then failing in container or network-mounted environments. Another issue is moving files without creating destination directories first. Teams also forget collision handling and overwrite files unexpectedly. Passing Path objects to older utility wrappers that expect strings can create subtle compatibility issues in mixed codebases. Finally, batch move scripts often ignore partial failures, which leads to hard-to-debug data gaps when some files moved and others did not.

Summary

  • Use shutil.move as the default for reliable file moves in Python.
  • Use os.rename when you specifically need same-filesystem rename behavior.
  • Add pre-checks for source existence and destination collisions.
  • Create destination directories explicitly before move operations.
  • Treat batch moves as operational workflows with logging, retries, and failure reporting.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.