ZetCode

Python os.replace Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.replace function, which atomically replaces files. We'll cover file renaming, cross-device moves, and practical file system operation examples.

Basic Definitions

The os.replace function atomically replaces the destination file with the source file. It works across different filesystems and handles most edge cases better than os.rename.

Key parameters: src (source file path), dst (destination file path). On Unix, this is equivalent to rename(2), while on Windows it uses MoveFileEx.

Basic File Replacement

The simplest use of os.replace replaces one file with another. If the destination exists, it will be overwritten atomically.

basic_replace.py
import os

# Create source file
with open("source.txt", "w") as f:
    f.write("This is the new content")

# Create destination file (will be replaced)
with open("destination.txt", "w") as f:
    f.write("This is the old content")

# Replace destination with source
os.replace("source.txt", "destination.txt")

# Verify replacement
with open("destination.txt") as f:
    print(f.read())  # Output: This is the new content

This example creates two files, then replaces the destination with the source. The operation is atomic - it either completely succeeds or fails.

After replacement, the source file no longer exists, and the destination contains the source file's content.

Replacing Across Directories

os.replace can move files between directories as long as they're on the same filesystem. This is more reliable than os.rename for cross-directory moves.

cross_directory.py
import os

# Create directories and files
os.makedirs("source_dir", exist_ok=True)
os.makedirs("dest_dir", exist_ok=True)

with open("source_dir/file.txt", "w") as f:
    f.write("File content")

# Replace across directories
os.replace("source_dir/file.txt", "dest_dir/file.txt")

# Verify the file was moved
print(os.listdir("source_dir"))  # Output: []
print(os.listdir("dest_dir"))    # Output: ['file.txt']

This moves a file from one directory to another atomically. The operation will fail if the directories are on different filesystems.

Unlike shutil.move, os.replace doesn't fall back to copy+delete when moving across filesystems.

Atomic File Updates

A common pattern is to write to a temporary file then atomically replace the target. This ensures the target is always in a consistent state.

atomic_update.py
import os
import tempfile

def update_file(filename, content):
    # Create temp file in same directory for atomic replace
    temp = tempfile.NamedTemporaryFile(
        dir=os.path.dirname(filename),
        delete=False
    )
    
    try:
        # Write new content to temp file
        with temp:
            temp.write(content.encode())
        
        # Atomically replace target file
        os.replace(temp.name, filename)
    except:
        # Clean up temp file if replace fails
        os.unlink(temp.name)
        raise

# Usage
update_file("important.log", "New log data")

This pattern ensures the target file always contains complete data, even if the system crashes during the write operation.

The temp file is created in the same directory as the target to ensure they're on the same filesystem.

Handling Existing Files

os.replace will overwrite existing files, but you may want to check permissions or existence first. This example shows proper handling.

existing_files.py
import os
import stat

def safe_replace(src, dst):
    # Check source exists and is readable
    if not os.access(src, os.R_OK):
        raise PermissionError(f"Cannot read source file {src}")
    
    # Check destination directory is writable
    dst_dir = os.path.dirname(dst) or "."
    if not os.access(dst_dir, os.W_OK):
        raise PermissionError(f"Cannot write to {dst_dir}")
    
    # Check if destination exists and is writable
    if os.path.exists(dst) and not os.access(dst, os.W_OK):
        raise PermissionError(f"Cannot overwrite {dst}")
    
    # Perform the atomic replace
    os.replace(src, dst)

# Usage
try:
    safe_replace("new_data.txt", "existing_data.txt")
except PermissionError as e:
    print(f"Error: {e}")

This wrapper function adds safety checks before performing the replace operation. It verifies all necessary permissions are available.

Note that between the checks and the actual replace, conditions could change (TOCTOU race condition).

Cross-Device Replacement

On Unix systems, os.replace may fail when moving between filesystems. This example shows how to handle such cases.

cross_device.py
import os
import shutil

def cross_device_replace(src, dst):
    try:
        os.replace(src, dst)
    except OSError as e:
        if e.errno == 18:  # EXDEV - cross-device link
            # Fall back to copy + delete
            shutil.copy2(src, dst)
            os.unlink(src)
        else:
            raise

# Usage
cross_device_replace("/mnt/volume1/file.txt", "/mnt/volume2/file.txt")

This function attempts a direct replace first, then falls back to copy+delete if the files are on different devices.

The shutil.copy2 preserves metadata, making it the best choice for a fallback operation.

Directory Replacement

While os.replace primarily works with files, it can also replace empty directories. This example demonstrates directory operations.

directory_replace.py
import os

# Create directories
os.makedirs("old_dir", exist_ok=True)
os.makedirs("new_dir", exist_ok=True)

# Add files to directories
with open("old_dir/file1.txt", "w") as f:
    f.write("Old content")

with open("new_dir/file1.txt", "w") as f:
    f.write("New content")

# Attempt to replace directories
try:
    os.replace("new_dir", "old_dir")
except OSError as e:
    print(f"Error: {e}")  # Output: [Errno 39] Directory not empty

# Works with empty directories
os.makedirs("empty_dir", exist_ok=True)
os.replace("empty_dir", "old_dir")  # Fails if old_dir not empty

This shows that directory replacement only works when the target is empty. For non-empty directories, consider shutil.rmtree first.

The behavior is platform-dependent, with Windows being more restrictive about directory operations.

Error Handling

os.replace can raise several exceptions that should be handled properly in production code. This example covers common cases.

error_handling.py
import os
import errno

def robust_replace(src, dst):
    try:
        os.replace(src, dst)
    except FileNotFoundError:
        print(f"Source file {src} does not exist")
    except PermissionError:
        print(f"Permission denied for {src} or {dst}")
    except OSError as e:
        if e.errno == errno.EXDEV:
            print("Cannot replace across filesystems")
        elif e.errno == errno.ENOTEMPTY:
            print("Target directory is not empty")
        else:
            print(f"Unexpected error: {e}")
    except Exception as e:
        print(f"Unexpected error: {e}")

# Usage examples
robust_replace("nonexistent.txt", "target.txt")
robust_replace("/root/file.txt", "target.txt")
robust_replace("/mnt/fs1/file.txt", "/mnt/fs2/file.txt")

This function handles the most common error cases for os.replace, providing meaningful error messages for each scenario.

Proper error handling is crucial for file operations that might fail due to permissions or filesystem constraints.

Security Considerations

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.