LESSON 90 · DEEPER PYTHON32 min

Replace a File Safely with a Temporary File

Replace a file only after a complete new value has been written to a temporary file.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Replace a File Safely with a Temporary File

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    A program updates a configuration file. The old content is valid, and the new content is valid, but the program can fail while writing. If it writes directly to the destination, a later reader may see half of a document. We need a different order: write the complete new content to a temporary file in the same directory, close it, and then replace the destination in one filesystem operation. Python's `tempfile` and `os.replace` give us the pieces. We will first reproduce the damaged-file result in an isolated temporary directory. Then we will inspect a sibling temporary file before replacement, perform a

  2. 00:40

    successful save, and simulate a failed save to see whether the old file survives. This lesson uses the word atomic in a specific, limited sense: readers see the old destination or the new destination at the replacement boundary on a normal local filesystem. It does not promise durability after sudden power loss, or identical behavior on every network filesystem. We will keep those limits visible rather than overstating the guarantee. Opening the destination in write mode truncates its old content before the new value is complete. The simulated exception happens after the prefix has been written.

  3. 01:20

    When we read the file again, it contains only `version=`. A consumer cannot safely use that as the old or new version. This is a controlled failure, but real interruptions can come from a full disk, encoding error, unexpected exception, process termination, or another failure in the surrounding application. A final write call does not retroactively protect the file during earlier writes. One tempting repair is to write the old content back in an exception handler. That assumes the program can still run the handler and that the old data is still available. A process crash or a second failure can defeat it.

  4. 02:00

    We should avoid exposing incomplete content at the destination in the first place. The disposable directory makes the demonstration safe; no real settings file is touched. The new content exists, but it is stored under a different name. The destination still reads `version=old` while the temporary file contains `version=new`. We put the temporary file in the destination's directory so the later replacement takes place on the same filesystem; placing it in an arbitrary system temp directory could cross a filesystem boundary. We also close the temporary file before the replacement step, which is especially

  5. 02:41

    important for portability on Windows. `delete=False` means the temporary file survives that close, so we must clean it up if anything fails before replacement. The final `unlink` here is only for this diagnosis run, where we intentionally do not promote the new content. Do not rely on a filename that looks unique enough by hand. `NamedTemporaryFile` creates one safely. The important observation is the separation of states: the old destination stays intact while the replacement candidate is prepared. The helper writes the candidate, closes it, and only then calls `os.replace`. The destination is not opened for an incomplete overwrite.

  6. 03:23

    When `os.replace` succeeds, the new file takes the destination name. `pending = None` records that there is no leftover temporary file to remove. If writing or replacement raises an exception, the `finally` block removes the candidate. The original destination is still available for a normal write failure before replacement. This helper is intentionally small. For data that must survive sudden power loss, additional steps such as flushing file content and directory metadata may be required and depend on the operating system and filesystem. Also consider preserving permissions and ownership when replacing an existing file;

  7. 04:04

    creating a new temporary file does not automatically copy every attribute of the old one. The lesson's task is to protect readers from the partial text we saw in the broken run, not to implement a complete database transaction system in twenty lines. The first candidate fails before replacement. We remove it, and the assertion confirms the old destination is unchanged. We also check that the leftover temporary file is gone. Only after that do we write a complete second candidate and replace the destination. The final content is new and complete. This test covers the ordinary exception path in Python; it does not simulate an

  8. 04:46

    abrupt machine shutdown. Try moving the temporary file to a different directory in the site console and think about why `os.replace` is intended to work within one filesystem. Try removing the cleanup line and see what orphan file remains after failure. Then explain the guarantee in your own words: until replacement, the old destination remains; after a successful replacement, readers open the new file. That is much stronger than writing directly to the destination, and it follows from the order of operations rather than from a magic flag. Complete the practice task by applying this sequence to a small settings file.

Understand the concept

Opening an existing file with mode 'w' truncates it immediately. If serialization or writing fails halfway through, readers may see an empty or partial file. A safer replacement pattern writes the complete new content to a temporary file, closes it, and only then changes the target name. This technique protects against many application-level failures before replacement, although power-loss durability requires additional filesystem steps.

Create the temporary file in the target's directory. On real filesystems, os.replace can then rename within the same filesystem, where replacement is commonly atomic. A temporary file in an unrelated directory may be on another filesystem and fail to rename. tempfile.NamedTemporaryFile(delete=False, dir=target.parent) gives a unique path; always close it before calling os.replace, especially for Windows compatibility.

A finally block should remove the temporary name if an exception happens before replacement. After successful os.replace, that old temporary pathname no longer exists, so the cleanup guard does nothing. The function should not silently create missing parent directories or change an application's file permissions policy; callers decide where it may write.

The website console has a temporary in-browser filesystem. These demonstrations are real file operations inside that sandbox, but they do not edit files on your computer and disappear with the session. The task tests a successful replacement and injects a failure at the replace step to verify that the previous target remains intact and no temporary name is abandoned.

  • Direct w mode truncates immediately
  • Write the temporary file in the target directory
  • Close before os.replace
  • Clean up temporary names after failure

See it step by step

Read the code, predict the output, then compare it with the result.

01. Observe direct overwrite

PYTHON
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    target.write_text('old', encoding='utf-8')
    target.write_text('new', encoding='utf-8')
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
new

This works on success but opens the real target for truncation before the new write finishes.

02. Write beside the target, then replace

PYTHON
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    temp = Path(folder) / 'note.tmp'
    target.write_text('old', encoding='utf-8')
    temp.write_text('new', encoding='utf-8')
    os.replace(temp, target)
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
new

The target changes only after the new file has been fully written and closed.

03. A failure before replace leaves the old file

PYTHON
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    target.write_text('old', encoding='utf-8')
    try:
        raise OSError('write failed before replace')
    except OSError:
        pass
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
old

A staged replacement leaves the previous target in place if work fails before the rename.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Notice what direct writing risks

The first run writes old text and then new text directly to the same path. It succeeds here and prints new, but opening an existing target for writing truncates it before the replacement content is complete. If formatting or writing fails afterward, the old value may already be gone.

The lesson is about keeping the previous file available until the new content has been fully staged. Use a temporary practice directory, as the example does, so experiments cannot touch valuable files. The website console's filesystem is temporary and separate from the visitor's computer; copied local code still deserves the same careful path selection.

PYTHON
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    target.write_text('old', encoding='utf-8')
    target.write_text('new', encoding='utf-8')
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
new
Follow the reasoning
  1. Write a known old value to an isolated target.
  2. Overwrite it directly and observe the success case.
  3. Identify the truncation window that success alone does not test.
02 / 03

Stage and replace only after writing finishes

The next example writes the new value to a sibling temporary path, closes it, and calls os.replace. The target reads new only after the complete temporary file exists. Keeping the temporary path in the same directory normally keeps both paths on one filesystem, which is important for rename-style replacement on a real machine.

A robust wrapper uses a unique temporary name rather than the fixed note.tmp used for this compact demonstration. Close the file before replacing it, especially on Windows. The pattern addresses failures before replacement; power-loss durability and cross-filesystem behavior need additional platform-specific care, so do not claim one short example solves every storage risk.

PYTHON
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    temp = Path(folder) / 'note.tmp'
    target.write_text('old', encoding='utf-8')
    temp.write_text('new', encoding='utf-8')
    os.replace(temp, target)
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
new
Follow the reasoning
  1. Create a temporary path beside the target.
  2. Write and close the complete new contents.
  3. Replace the target name only after staging succeeds.
03 / 03

Keep the old file after a staged failure

The last fixture raises an error before any replacement and then reads old from the target. That is the outcome the checked function should preserve when writing or os.replace fails. The previous file remains the valid version, while a finally block can remove an abandoned temporary file name.

Test both the success path and an injected failure. Verify the target's contents and inspect the directory for leftover temporary files. The function should not silently create missing parent directories or alter an application's permissions policy. Its promise is narrower and useful: stage a complete value and leave the original intact if work fails before replacement.

PYTHON
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
    target = Path(folder) / 'note.txt'
    target.write_text('old', encoding='utf-8')
    try:
        raise OSError('write failed before replace')
    except OSError:
        pass
    print(target.read_text(encoding='utf-8'))
EXPECTED OUTPUT
old
Follow the reasoning
  1. Trigger a failure before changing the target path.
  2. Confirm the original contents remain readable.
  3. Clean up the temporary name on either outcome.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnImplement atomic_write_text(target, text): write UTF-8 text to a NamedTemporaryFile in target.parent, close it, call os.replace, and remove the temporary name if replacement fails. The prepared output is new; a simulated replace failure must leave old content intact.
Need a hint?

Convert target to Path. Keep a temp_name initialized to None. In a try/finally, open NamedTemporaryFile(mode='w', encoding='utf-8', dir=target.parent, delete=False), write, close, then os.replace(temp_name, target). In finally, unlink temp_name only if it still exists.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.When does opening an existing file with 'w' truncate it?

    02.Why create the temporary file in target.parent?

    03.Does os.replace alone guarantee power-loss durability?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Writing directly to the final path

    COMMON MISTAKE
    Path(target).write_text(text, encoding='utf-8')
    FIX
    write a temporary file, close it, then os.replace(temp_name, target)

    What happens: The old content is lost if writing fails partway.
    Stage complete content before changing the target name.

    Leaving a failed temporary file behind

    COMMON MISTAKE
    temp = NamedTemporaryFile(delete=False); temp.write(data); os.replace(temp.name, target)
    FIX
    close the temporary file; use finally to unlink its name if it still exists

    What happens: An exception can leave an orphaned name and an open file.
    Failure cleanup is part of the replacement pattern.

    Browse this lesson’s problem fixes

    Sources and further reading