Python - Create/Delete Directory

The `os.mkdir()` function creates a single directory. It fails if the parent directory doesn't exist or if the directory already exists.

Key Insights

  • Python provides multiple methods for directory operations through os, pathlib, and shutil modules, each with different levels of abstraction and capability
  • Creating directories requires handling parent path existence, while deletion demands careful consideration of directory contents and error conditions
  • Modern Python (3.4+) favors pathlib.Path for its object-oriented approach and cross-platform compatibility, though os and shutil remain essential for advanced operations

Basic Directory Creation with os.mkdir()

The os.mkdir() function creates a single directory. It fails if the parent directory doesn’t exist or if the directory already exists.

import os

# Create a single directory
os.mkdir('data')

# Create a directory with specific permissions (Unix-like systems)
os.mkdir('logs', mode=0o755)

# Attempting to create nested directories fails
try:
    os.mkdir('project/src/utils')  # Raises FileNotFoundError
except FileNotFoundError as e:
    print(f"Error: {e}")

The mode parameter sets directory permissions using octal notation. On Windows, this parameter is ignored. The default mode is 0o777 (read, write, execute for all users), modified by the system’s umask.

Creating Nested Directories with os.makedirs()

When you need to create multiple directory levels at once, os.makedirs() handles the entire path hierarchy.

import os

# Create nested directories
os.makedirs('project/src/utils')

# Use exist_ok to prevent errors if directory exists
os.makedirs('project/data/raw', exist_ok=True)

# Create multiple directory structures
paths = [
    'app/controllers',
    'app/models',
    'app/views',
    'config',
    'tests'
]

for path in paths:
    os.makedirs(path, exist_ok=True)

The exist_ok=True parameter (Python 3.2+) prevents FileExistsError when the directory already exists. Without it, you’d need explicit exception handling.

Modern Approach with pathlib.Path

The pathlib module provides an object-oriented interface for filesystem paths, offering cleaner syntax and better cross-platform compatibility.

from pathlib import Path

# Create a single directory
Path('data').mkdir()

# Create nested directories
Path('project/src/utils').mkdir(parents=True, exist_ok=True)

# Create multiple directories with better error handling
base_dir = Path('application')
subdirs = ['static', 'templates', 'uploads', 'cache']

for subdir in subdirs:
    (base_dir / subdir).mkdir(parents=True, exist_ok=True)

# Check if directory exists before creating
config_dir = Path('config')
if not config_dir.exists():
    config_dir.mkdir()
    print(f"Created {config_dir}")
else:
    print(f"{config_dir} already exists")

The parents=True parameter functions like os.makedirs(), creating intermediate directories. The / operator elegantly joins path components.

Deleting Empty Directories with os.rmdir()

The os.rmdir() function removes only empty directories. It raises OSError if the directory contains files or subdirectories.

import os

# Remove an empty directory
os.rmdir('temp')

# Attempting to remove non-empty directory fails
try:
    os.rmdir('project')  # Raises OSError if not empty
except OSError as e:
    print(f"Cannot remove directory: {e}")

# Safe deletion with existence check
if os.path.exists('cache') and not os.listdir('cache'):
    os.rmdir('cache')
    print("Cache directory removed")

Deleting Directory Trees with shutil.rmtree()

For removing directories with contents, shutil.rmtree() recursively deletes all files and subdirectories.

import shutil
import os

# Remove directory and all contents
shutil.rmtree('old_project')

# Safe deletion with existence check
if os.path.exists('temp_data'):
    shutil.rmtree('temp_data')

# Handle permission errors with error handler
def handle_remove_error(func, path, exc_info):
    """Error handler for rmtree"""
    print(f"Error removing {path}: {exc_info[1]}")
    # Optionally, change permissions and retry
    os.chmod(path, 0o777)
    func(path)

shutil.rmtree('protected_dir', onerror=handle_remove_error)

# Ignore errors completely (use cautiously)
shutil.rmtree('maybe_exists', ignore_errors=True)

The onerror parameter accepts a callback function that receives the failing function, path, and exception info. This enables custom error handling for permission issues or locked files.

Deleting with pathlib.Path

The pathlib module provides rmdir() for empty directories and requires combining with other methods for recursive deletion.

from pathlib import Path
import shutil

# Remove empty directory
Path('empty_folder').rmdir()

# Remove directory tree using shutil
project_dir = Path('old_project')
if project_dir.exists():
    shutil.rmtree(project_dir)

# Iterate and delete files before removing directory
cache_dir = Path('cache')
if cache_dir.exists():
    for item in cache_dir.iterdir():
        if item.is_file():
            item.unlink()
        elif item.is_dir():
            shutil.rmtree(item)
    cache_dir.rmdir()

Python 3.10+ adds Path.rmdir() with a recursive parameter, but shutil.rmtree() remains more widely compatible.

Practical Error Handling Patterns

Robust directory operations require comprehensive error handling for various failure scenarios.

import os
import shutil
from pathlib import Path
import errno

def create_directory_safe(path):
    """Create directory with comprehensive error handling"""
    try:
        Path(path).mkdir(parents=True, exist_ok=True)
        return True
    except PermissionError:
        print(f"Permission denied: {path}")
        return False
    except OSError as e:
        print(f"OS error creating {path}: {e}")
        return False

def remove_directory_safe(path):
    """Remove directory with retry logic"""
    path_obj = Path(path)
    
    if not path_obj.exists():
        return True
    
    try:
        if path_obj.is_file():
            path_obj.unlink()
        else:
            shutil.rmtree(path_obj)
        return True
    except PermissionError:
        print(f"Permission denied: {path}")
        # Attempt to change permissions and retry
        try:
            os.chmod(path, 0o777)
            shutil.rmtree(path)
            return True
        except Exception as e:
            print(f"Failed to remove after permission change: {e}")
            return False
    except Exception as e:
        print(f"Error removing {path}: {e}")
        return False

# Usage
if create_directory_safe('data/processed'):
    print("Directory created successfully")

if remove_directory_safe('temp'):
    print("Directory removed successfully")

Working with Temporary Directories

Python’s tempfile module creates temporary directories that automatically clean up.

import tempfile
import shutil
from pathlib import Path

# Create temporary directory with context manager
with tempfile.TemporaryDirectory() as temp_dir:
    temp_path = Path(temp_dir)
    
    # Use temporary directory
    (temp_path / 'data.txt').write_text('temporary data')
    print(f"Working in: {temp_dir}")
    
    # Directory automatically deleted when exiting context

# Manual temporary directory management
temp_dir = tempfile.mkdtemp(prefix='myapp_')
try:
    # Perform operations
    Path(temp_dir, 'output.log').write_text('logs')
finally:
    # Clean up manually
    shutil.rmtree(temp_dir)

# Create temporary directory in specific location
temp_dir = tempfile.mkdtemp(dir='/var/tmp', prefix='process_')

Cross-Platform Considerations

Directory operations behave differently across operating systems, requiring platform-aware code.

import os
import sys
from pathlib import Path

# Use Path for cross-platform compatibility
data_dir = Path.home() / 'application_data'
data_dir.mkdir(parents=True, exist_ok=True)

# Platform-specific directory creation
if sys.platform == 'win32':
    app_dir = Path(os.environ['APPDATA']) / 'MyApp'
else:
    app_dir = Path.home() / '.myapp'

app_dir.mkdir(parents=True, exist_ok=True)

# Handle path separators correctly
config_path = Path('config') / 'settings' / 'database.ini'
config_path.parent.mkdir(parents=True, exist_ok=True)

The pathlib module automatically handles path separators and other platform differences, making it the preferred choice for cross-platform applications. Always use Path objects or os.path.join() instead of hardcoding separators.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.