skip to content

What do open()'s 'w', 'a' and 'x' modes each do to an existing file?

level: juniorimportance: must knowfreq 70%

answer

  1. One letter decides create-or-destroy
  2. Two of them will make the file
  3. One of them empties it immediately
  4. One refuses when the file exists
  5. '+' adds a direction, never truncation

basics

~10 s

'w' truncates the file to zero bytes, creating it if absent. 'a' leaves the contents alone and writes at the end. 'x' refuses to touch an existing file and raises FileExistsError.

solid answer

~40 s

The mode string passed to `open()` picks exactly one creation behaviour. `'r'` (the default) requires the file to exist and raises `FileNotFoundError` otherwise. `'w'` creates the file or **truncates it to zero bytes at open time**, before you have written anything. `'a'` creates the file if needed and forces every write to the current end, so a `seek()` does not move the write position back. `'x'` is exclusive creation: it succeeds only if the file did not exist, raising `FileExistsError` if it did, and the check-and-create happens in one operation in the OS rather than as a separate existence test. A `'b'` makes the stream binary (`bytes` in and out) and a `'+'` adds the missing direction, so `'r+'` reads and writes without truncating while `'w+'` still truncates.

code

python · 19 lines
python
import os

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("first\n")

with open("notes.txt", "a", encoding="utf-8") as f:
    f.write("second\n")

with open("notes.txt", encoding="utf-8") as f:
    print(f.read())

try:
    open("notes.txt", "x", encoding="utf-8")
except FileExistsError as exc:
    print("x refused:", exc.filename)

with open("notes.txt", "w", encoding="utf-8"):
    pass
print("size after w:", os.path.getsize("notes.txt"))

go deeper

for a junior

Memorise what each letter does to a file that already exists: 'r' reads it, 'w' empties it, 'a' adds to the end, 'x' refuses. Know that 'r' is the default and that 'b' switches you from str to bytes.

for a middle

Explain the mechanics: truncation happens at open time, append is enforced per write by the OS flag, and '+' adds a direction without changing creation behaviour. Be able to say why 'w+' is not the safe read-write mode people assume.

for a senior

Show judgement about data loss. Justify 'x' over check-then-open for anything another process might create, and explain why replacing a file people are reading means writing a new name rather than truncating the original.

for a principal

Own the convention across a codebase: which paths may ever be opened 'w', where appends are the contract for multiple writers, and how the team stops an incidental 'w' from being the reason a night's export is empty.

`open()`'s second argument is not an enum but a short string of independent flags. Exactly one character chooses the *creation and access* behaviour — `r`, `w`, `a` or `x`; an optional `b` or `t` chooses binary or text (text is the default); and an optional `+` adds the direction the base letter lacks. `open(path)` therefore means `open(path, "rt")`. ## The four base modes **`'r'` — read an existing file.** The file must exist; otherwise you get `FileNotFoundError`. Nothing is created and nothing is modified. This is the default, which is why a bare `open(path)` never destroys data. **`'w'` — create or truncate.** If the file does not exist it is created. If it does exist, it is emptied *at the moment of the open call*, not when you first write. That timing is the part candidates miss: if your program opens a file for writing and then raises before writing anything, you have replaced a good file with an empty one. Anywhere the old contents still matter, write to a new name and swap it into place rather than truncating the original. **`'a'` — append.** The file is created if missing, and existing bytes are never removed. The important detail is that appending is a property of every write, not just of the initial position: the stream is opened with the OS-level append flag, so each write goes to the current end of file even if you called `seek()` first. Two processes appending short records to the same file therefore do not overwrite each other's bytes the way two `'w'`-mode writers would. In `'a+'` you can read as well, and there `seek()` does move the *read* position while writes still land at the end. **`'x'` — exclusive creation.** Succeeds only if the file did not already exist, and raises `FileExistsError` if it did. It is worth preferring over the pattern of testing for existence first and then opening with `'w'`: between your test and your open, another process can create the file, and you silently clobber it. `'x'` pushes the test and the create into a single OS operation, so the loser of the race gets an exception instead of destroying the winner's file. Lock files, run-once markers and "do not overwrite an export that already exists" checks are its natural home. ## The modifiers **`'b'` versus text.** Without `b` you get a text stream: you read and write `str`, the bytes are decoded and encoded through a codec, and line endings are translated. With `b` you get raw `bytes`, no codec and no line-ending translation. A binary-mode open also rejects the `encoding` and `newline` arguments with `ValueError`, which is a useful reminder that those two only exist for text. **`'+'`.** `'r+'` opens an existing file for reading and writing and leaves the contents intact, positioned at the start; `'w+'` also reads and writes but still truncates first; `'a+'` reads and appends; `'x+'` creates exclusively and allows reading. The `+` never changes whether the file is truncated or created — that is entirely the base letter's job. This is where the common mix-up lives: people reach for `'w+'` when they meant "open it for read and write" and are surprised the file is empty. ## Choosing in practice Ask two questions. First, *must the file already exist?* If yes, `'r'` or `'r+'`. If it must **not** exist, `'x'`. If you do not care, `'w'` or `'a'`. Second, *do I want the old contents?* If yes, `'a'` or `'r+'`; if no, `'w'`. A few consequences follow directly. `'w'` is the mode that loses data, so treat every `'w'` on a path a user cares about as a decision, not a default. `'a'` is the mode for logs and for anything appended by more than one writer. `'x'` is the mode that turns a race into an exception. And because the truncation in `'w'` happens at open time, holding the file open for a long stretch of work while other readers watch it is a design choice you should make deliberately rather than by accident. Finally, none of these letters say anything about *when* your bytes reach the disk — that is the stream's buffering, a separate concern from the mode. The mode only decides what exists when the call returns and where writes are allowed to land.

  • What is the difference between opening a file with 'r+' and with 'w+'?
    Both give a readable and writable stream. `'r+'` requires the file to exist, keeps its contents and starts at offset zero, so you can overwrite parts of it in place. `'w+'` creates the file if needed and truncates it to zero bytes first, so there is never anything to read until you have written it. The `+` only adds a direction; the base letter still decides truncation.
  • Why is opening with 'x' better than checking whether the path exists and then opening with 'w'?
    The check-then-open pattern has a window between the two steps in which another process can create the file, and your `'w'` then truncates it. `'x'` asks the operating system to create the file only if it does not exist, as a single indivisible operation, so the loser of that race gets `FileExistsError` rather than silently destroying the winner's data.
  • What happens if you pass encoding= to a file opened in binary mode?
    `open()` raises `ValueError`. Binary mode hands you raw `bytes` with no codec and no line-ending translation, so `encoding` and `newline` are meaningless there and are rejected rather than ignored. If you need decoded text you either open in text mode or decode the bytes yourself.

'w' is handing the clerk a fresh blank form, 'a' is adding a line to the bottom of the existing form, and 'x' is being told there is already a form on file and you may not start a second one.

saying these in an interview costs you the question

  • Says 'w' appends when the file already exists
  • Thinks 'w' only truncates once you write
  • Believes seek() moves the write position in 'a' mode
  • Thinks 'r' creates the file when it is missing
  • Treats 'w+' as a non-destructive read-write mode
  • Thinks the mode letters control flushing to disk

context