Skip to main content

How to Delete All PNG Images in a Folder with Python

Cleaning up image files by extension is a common task when managing datasets, temporary assets, or build artifacts. Python's pathlib module provides a clean, modern approach for finding and deleting files by pattern, while the older os module offers compatibility with earlier Python versions.

In this guide, you will learn how to delete PNG files from a single folder or recursively through subdirectories, handle case variations, filter by size, implement a dry run for safety, and build a reusable cleanup function.

The pathlib module provides an object-oriented interface for filesystem operations. The glob() method finds all files matching a pattern, and unlink() deletes them:

from pathlib import Path

target_dir = Path("./images")

for image in target_dir.glob("*.png"):
try:
image.unlink()
print(f"Deleted: {image.name}")
except OSError as e:
print(f"Failed to delete {image.name}: {e}")

Example output:

Deleted: screenshot.png
Deleted: thumbnail.png
Deleted: banner.png

Wrapping each deletion in a try/except block ensures the script continues even if a specific file cannot be deleted due to permissions or other issues.

Recursive Deletion with rglob()

To delete PNG files in the target folder and all its subdirectories, use rglob() instead of glob():

from pathlib import Path

target_dir = Path("./assets")

deleted_count = 0
for image in target_dir.rglob("*.png"):
try:
image.unlink()
deleted_count += 1
print(f"Deleted: {image}")
except OSError as e:
print(f"Error: {e}")

print(f"\nTotal deleted: {deleted_count}")

Example output:

Deleted: assets/icons/logo.png
Deleted: assets/banners/header.png
Deleted: assets/temp/preview.png

Total deleted: 3
warning

rglob() searches all subdirectories recursively. Always verify the target path carefully before running recursive deletions to avoid removing important files in nested folders you did not intend to touch.

Dry Run Before Deletion

Deleting files is irreversible (unless you use a trash mechanism). Always implement a dry run option that previews what would be deleted before actually removing anything:

from pathlib import Path

def delete_images(folder: str, extension: str = ".png", dry_run: bool = True) -> int:
"""Delete image files with an optional dry run."""
target = Path(folder)

if not target.exists():
print(f"Folder not found: {folder}")
return 0

files = list(target.glob(f"*{extension}"))

if not files:
print("No matching files found.")
return 0

if dry_run:
print(f"Would delete {len(files)} file(s):")
for f in files:
print(f" {f.name}")
else:
for f in files:
f.unlink()
print(f"Deleted: {f.name}")

return len(files)

# Preview first
delete_images("./images", ".png", dry_run=True)

# Then actually delete (uncomment when ready)
# delete_images("./images", ".png", dry_run=False)

Example output (dry run):

Would delete 3 file(s):
screenshot.png
thumbnail.png
banner.png
tip

Always implement a dry run option for deletion scripts. This prevents accidental data loss when testing or running the script in a new environment for the first time.

Case-Insensitive Matching

File extensions can vary in case: .png, .PNG, .Png. The glob() method is case-sensitive on most operating systems, so you need to handle variations explicitly:

from pathlib import Path

target_dir = Path("./images")

for image in target_dir.iterdir():
if image.suffix.lower() == ".png":
image.unlink()
print(f"Deleted: {image.name}")

Using iterdir() with a manual suffix check gives you full control over case handling, ensuring that files like Photo.PNG and icon.Png are caught along with standard .png files.

Deleting Multiple Image Types

To remove several image formats at once, define a set of target extensions and check each file against it:

from pathlib import Path

target_dir = Path("./temp_images")
extensions = {".png", ".jpg", ".jpeg", ".gif", ".bmp"}

deleted = 0
for file in target_dir.iterdir():
if file.suffix.lower() in extensions:
file.unlink()
deleted += 1
print(f"Deleted: {file.name}")

print(f"\nTotal deleted: {deleted}")

Example output:

Deleted: photo.jpg
Deleted: icon.png
Deleted: animation.gif

Total deleted: 3

Using a set for extensions provides O(1) lookup time, which is efficient even with a long list of target formats.

Filtering by File Size

Sometimes you only want to delete files that meet certain size criteria, such as removing small thumbnails while keeping full-resolution images:

from pathlib import Path

target_dir = Path("./images")
max_size = 10 * 1024 # 10 KB

for image in target_dir.glob("*.png"):
file_size = image.stat().st_size
if file_size < max_size:
image.unlink()
print(f"Deleted small image: {image.name} ({file_size:,} bytes)")

Example output:

Deleted small image: thumb_01.png (3,412 bytes)
Deleted small image: thumb_02.png (4,891 bytes)

Moving to Trash Instead of Permanent Deletion

For recoverable deletion, use the send2trash library, which moves files to the system's recycle bin or trash instead of permanently removing them:

pip install send2trash
from pathlib import Path
from send2trash import send2trash

target_dir = Path("./images")

for image in target_dir.glob("*.png"):
send2trash(str(image))
print(f"Moved to trash: {image.name}")

Example output:

Moved to trash: screenshot.png
Moved to trash: banner.png

This is a safer option during development or when you are not completely sure about which files should be removed.

Using the os Module (Legacy Approach)

For compatibility with older Python versions that do not have pathlib, use the os module:

import os

folder = "./images"

for filename in os.listdir(folder):
if filename.lower().endswith(".png"):
filepath = os.path.join(folder, filename)
try:
os.remove(filepath)
print(f"Deleted: {filename}")
except OSError as e:
print(f"Error deleting {filename}: {e}")

For recursive deletion with os.walk():

import os

root_folder = "./assets"

for dirpath, dirnames, filenames in os.walk(root_folder):
for filename in filenames:
if filename.lower().endswith(".png"):
filepath = os.path.join(dirpath, filename)
os.remove(filepath)
print(f"Deleted: {filepath}")

Complete Reusable Cleanup Function

Here is a comprehensive function that combines all the techniques covered above into a flexible, reusable tool:

from pathlib import Path

def cleanup_images(
folder: str,
extensions: set[str] = {".png"},
recursive: bool = False,
min_size: int = 0,
max_size: int | None = None,
dry_run: bool = True
) -> dict:
"""
Delete image files with filtering options.

Returns a dictionary of statistics about the operation.
"""
target = Path(folder)
stats = {"scanned": 0, "deleted": 0, "skipped": 0, "bytes_freed": 0}

if not target.exists():
raise FileNotFoundError(f"Folder not found: {folder}")

iterator = target.rglob("*") if recursive else target.iterdir()

for file in iterator:
if not file.is_file():
continue

if file.suffix.lower() not in extensions:
continue

stats["scanned"] += 1
file_size = file.stat().st_size

if file_size < min_size or (max_size and file_size > max_size):
stats["skipped"] += 1
continue

if dry_run:
print(f"Would delete: {file} ({file_size:,} bytes)")
else:
try:
file.unlink()
stats["bytes_freed"] += file_size
print(f"Deleted: {file}")
except OSError as e:
print(f"Error: {e}")
stats["skipped"] += 1
continue

stats["deleted"] += 1

return stats

# Usage
stats = cleanup_images(
"./images",
extensions={".png", ".gif"},
recursive=True,
dry_run=True
)

print(f"\nScanned: {stats['scanned']}")
print(f"Would delete: {stats['deleted']}")
print(f"Skipped: {stats['skipped']}")

Method Comparison

MethodRecursiveSyntaxPython Version
Path.glob("*.png")NoClean, modern3.4+
Path.rglob("*.png")YesClean, modern3.4+
os.listdir() + os.remove()NoVerboseAny
os.walk() + os.remove()YesVerboseAny
send2trash()Depends on usageCleanAny (requires install)

Conclusion

  • Use pathlib for modern, readable file deletion code. The glob() method handles pattern matching elegantly, rglob() adds recursive support, and unlink() provides clear intent for file removal.
  • Always implement a dry run option to preview deletions before they happen, handle case variations in file extensions by comparing with .lower(), and wrap deletions in try/except blocks to handle permission errors gracefully.
  • For situations where recovery might be needed, consider using send2trash to move files to the system trash instead of permanently deleting them.