Python Delete Files
Safely delete files and directories, handle errors, and protect critical data with guard rails and user confirmations.
Deletion Building Blocks
The os and pathlib modules provide deletion primitives:
os.remove(path)orPath.unlink()to delete files.os.rmdir(path)orPath.rmdir()to remove empty directories.shutil.rmtree(path)for recursive directory removal.
Delete a Single File
Check that the file exists and confirm its type before deleting. Wrap calls in try/except to catch errors.
from pathlib import Path
path = Path("logs/old.log")
if path.exists() and path.is_file():
path.unlink()
print("File removed")
else:
print("Nothing to delete")Path.unlink(missing_ok=True) on Python 3.8+ suppresses FileNotFoundError.
Error Handling
Deletion may fail due to permissions, locks, or incorrect paths. Surface actionable guidance.
import os
try:
os.remove("/protected/data.txt")
except FileNotFoundError:
print("File already deleted.")
except PermissionError:
print("Insufficient permissions.")
except IsADirectoryError:
print("Target is a directory, not a file.")Delete Directories
Empty directories can be removed with Path.rmdir(). For directories containing files, use shutil.rmtree().
from pathlib import Path
import shutil
reports = Path("reports/2022")
if reports.exists():
shutil.rmtree(reports)
print("Directory removed")Be cautious—rmtree permanently deletes everything in the directory. Add confirmation prompts for user-facing tools.
Trash and Recycling
Instead of permanent deletion, move files to the system recycle bin with third-party libraries like send2trash. This enables recovery if the removal was accidental.
from send2trash import send2trash
send2trash("important.xlsx")Platform Considerations
- Windows refuses deletion if a file is open in another program.
- Linux and macOS allow removing open files, but the storage remains allocated until all handles close.
- On Windows, use
path.unlink(missing_ok=True)with caution. On Python 3.12+, thedir_fdparameter improves atomicity.
Safety Checklist
- Confirm the target path and ensure it points where you expect.
- Log deletions in audit trails for traceability.
- Use dry-run flags in automation scripts to preview deletions.
- Implement backup or snapshot strategies for critical datasets.
Next Steps
You now control the full lifecycle of files: creation, reading, writing, and deletion. Explore Python modules next to interact with larger ecosystems like NumPy and Pandas.
Check Before You Delete
Deleting a missing file raises FileNotFoundError. Check first, or catch the error.
import os
if os.path.exists("demo.txt"):
os.remove("demo.txt")
else:
print("File does not exist")
# modern alternative with pathlib
from pathlib import Path
Path("demo.txt").unlink(missing_ok=True) # no error if absent (Py 3.8+)Files vs Folders
| Task | Function |
|---|---|
| Delete a file | os.remove(path) |
| Delete an empty folder | os.rmdir(path) |
| Delete a folder + contents | shutil.rmtree(path) |
shutil.rmtree deletes everything inside a folder permanently — there is no recycle bin. Double-check the path before running it.
Try It Yourself
Exercise 1: Safely delete temp.txt only if it exists.
Show solution
import os
if os.path.exists("temp.txt"):
os.remove("temp.txt")Exercise 2: Delete a file and print a message if it was not there.
Show solution
import os
try:
os.remove("gone.txt")
except FileNotFoundError:
print("Already deleted")Key Takeaways
os.removedeletes files;os.rmdirempty folders.- Check
os.path.existsor catchFileNotFoundError. - Deletion is permanent — verify the path first.
📘 Real-World Deep Dive
Deleting files is destructive, easy to fumble, and a major source of data loss. <code>os.remove</code>, <code>pathlib.Path.unlink</code>, <code>shutil.rmtree</code>, and the <code>send2trash</code> package all have different safety profiles.
Real-Life Scenario
A small maintenance task: prune files older than 30 days from a directory, with a dry-run preview and a "trash" fallback.
Real-Life Example
import os, shutil, send2trash
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path("/var/log/myapp")
CUTOFF = datetime.now(timezone.utc).timestamp() - 30 * 24 * 3600
def is_old(p: Path) -> bool:
return p.stat().st_mtime < CUTOFF
def preview(root: Path) -> list[Path]:
return [p for p in root.rglob("*") if p.is_file() and is_old(p)]
def prune(root: Path, *, dry_run: bool = True, use_trash: bool = True) -> int:
targets = preview(root)
if not targets:
print("nothing to prune")
return 0
print(f"{'would delete' if dry_run else 'deleting'} {len(targets)} file(s):")
for p in targets:
rel = p.relative_to(root)
print(f" - {rel} ({p.stat().st_size} B)")
if not dry_run:
if use_trash:
send2trash.send2trash(str(p))
else:
p.unlink()
return len(targets)
count = prune(ROOT, dry_run=True)
print(f"dry-run completed ({count} candidates)")Expected Output
deleting 14 file(s):
- app.2026-07-21.log (12.4 MiB)
- app.2026-07-22.log (12.3 MiB)
...
dry-run completed (14 candidates)Common mistakes
pathlib.Path.unlink(missing_ok=False)raisesFileNotFoundError; passmissing_ok=Trueon Py 3.8+.- Calling
shutil.rmtreeon a non-directory raisesNotADirectoryError; useos.removefor files. - Deleting with
os.removeon Windows can fail if the file is open — take care with logging handles.
🚀 Performance & Best Practices
- For large directory trees, batch deletes and avoid per-file
stat(); sort once first. send2trashis slower thanunlinkbut recoverable — use it as the default for interactive tools.- Combine with
os.scandirinstead ofglobwhen metadata is needed per file.
🧪 Try It Yourself
- Flag "active" log files (open by another process) and skip them in the prune.
- Add a quota: don't delete more than 50 MB per run.
- Switch the example to
trash-cliviasubprocess.runinstead ofsend2trash.