Q1Which of the following do you use when you want to recursively delete an entire folder?
shutil and tempfile — Bulk File Ops and Temp Files
Learn Python's shutil and tempfile from the ground up. Bulk file operations with shutil.copy / shutil.move / shutil.rmtree, plus how to safely use temporary files via tempfile.NamedTemporaryFile — all hands-on.
This article walks through two modules: shutil, which operates on whole files and folders, and tempfile, which handles disposable temporary files. You'll cover bulk file operations with shutil.copy / move / rmtree, the safe way to use NamedTemporaryFile, and the atomic write pattern that combines both.
shutil — File and Folder Operations in One Line
shutil stands for shell utilities — it's the standard library that lets you call shell-style operations like cp / mv / rm -r from Python in a single line. The os module has things like os.rename, but shutil adds higher-level operations such as recursive folder copy and tree delete.
| Function | Behavior | Notes |
|---|---|---|
| shutil.copy(src, dst) | Duplicate a file | If dst is a folder, it goes inside with the same name |
| shutil.copy2(src, dst) | copy plus metadata like modification time | Good for backups |
| shutil.copytree(src, dst) | Recursively duplicate an entire folder | dst **must not exist** |
| shutil.move(src, dst) | Move or rename a file or folder | Equivalent to rename when src and dst share the same parent |
| shutil.rmtree(path) | Recursively delete a folder and its contents | Not reversible — double-check the target before running |
import shutil
import os
# 1) Copy a file to another location (a typical backup)
os.makedirs("backups", exist_ok=True) # Make sure the destination folder exists
shutil.copy("data/sales.csv", "backups/sales_2024.csv")
shutil.move("logs/temp.log", "logs/archive_2024.log")
shutil.rmtree("old_data")
rmtree Cannot Be Undone
shutil.rmtree(path) physically deletes everything under path. Python doesn't route through a trash bin — it's gone instantly. In a real project, print the target before deleting, add a guard like if path.startswith("/tmp"), and build habits that protect both you and your code.
tempfile — Safely Create Disposable Files
tempfile is the standard library for temporary files that are automatically cleaned up when you're done with them. You use it for storing intermediate results, buffering large data, holding test working folders — anything that only needs to exist while the process is running.
If you craft your own filenames like tmp_xxx.txt, you risk colliding with other processes or forgetting to delete them. tempfile instead creates files in the OS's temp folder under a guaranteed-unique name, and removes them automatically when the work is done.
with block, and is deleted automatically when you leave it. The filename is assigned on the spot to avoid collisions — you don't pick it yourself.| Function / attribute | Meaning | Notes |
|---|---|---|
| tempfile.NamedTemporaryFile | Temp file you open and close with `with` | delete=False keeps it after the block |
| tempfile.mkdtemp() | Returns the path of a temp folder | You delete it yourself with shutil.rmtree |
| tempfile.gettempdir() | Returns the OS temp folder path | /tmp on Linux, different path on macOS |
| tf.name | The actual file path (random name) | Accessible inside the `with` block |
| tf.write / tf.read | Same API as a regular file object | Pick 'w' / 'r' / 'w+' via the mode argument |
import tempfile
import os
# Open safely with `with`, then auto-delete
with tempfile.NamedTemporaryFile(mode="w+", suffix=".txt", delete=True) as tf:
tf.write("intermediate aggregation data\n")
tf.seek(0)
print("Content:", tf.read())
print("Path:", tf.name)
# ← The block ends here and the file is auto-deleted
When to Combine delete=False with `with`
When you want to write the file and read it back later from somewhere else, pass delete=False so the file survives leaving the with block. Once you're done with it, either remove it yourself with os.remove(path) or swap it into the real destination with shutil.move — that's the atomic write pattern covered below.
Auto-Deleted Temp Files with `delete=True`
If you overwrite an important config file or state file directly with open("settings.json", "w"), a process crash mid-write can leave a half-written file behind, which then fails to parse on the next read. The standard way to avoid this is the "write to a temp file completely, then rename / move it into the real path" pattern — known as atomic write (atomic — even if interrupted, observers can only see "completed" or "not started", never an in-between state).
import tempfile
import shutil
import json
final_path = "settings.json"
new_settings = {"theme": "dark", "lang": "en", "fontSize": 14}
# 1) Write fully to a temp file
with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".json") as tf:
json.dump(new_settings, tf)
temp_path = tf.name
# 2) Once finished, move into the real path (this single moment is the "swap")
shutil.move(temp_path, final_path)
# 3) Verify
with open(final_path) as f:
print(json.load(f))
# → {'theme': 'dark', 'lang': 'en', 'fontSize': 14}
A file object has a current read/write position (cursor), and calling `tf.write(...)` moves the cursor right after what was written (= the end). Calling tf.read() from there returns an empty string because there's nothing past the cursor, so the standard move is to reset the cursor to the start with `tf.seek(0)` before reading.
mode="w+" Is Read-Write Mode
`mode="w+"` is the mode that lets you both write and read through the same file handle. With just "w" you're write-only and read() doesn't work; with just "r" you're read-only and write() doesn't work. For exercises that write and immediately read back, "w+" is required.
Knowledge Check
Answer each question one by one.
Q2Which is a typical reason to use tempfile.NamedTemporaryFile with delete=False?
Q3What's the essence of atomic write?