File modes, binary vs. text files, and encoding
corebeginneropen(path, "r") reads text and decodes bytes to str using an encoding — always pass encoding="utf-8" explicitly. open(path, "rb") reads raw bytes with no decoding at all — no encoding applies.
Think of it as
A file on disk is always bytes — text mode is a translation layer Python adds on read/write, converting bytes to str using an encoding, and back again on write. Binary mode skips that translation entirely and hands you the raw bytes. Choosing the wrong mode either double-translates data that was never text (corrupting it) or leaves you manually decoding bytes that "r" mode would have handled for you.
What we're doing: Write a file with pathlib in text mode, read it back as both bytes and str, then trigger a real UnicodeDecodeError by reading UTF-8 bytes with the wrong encoding.
- 4
- "café".encode("utf-8") produces the real multi-byte UTF-8 representation of é, written to disk as raw bytes.
- 6
- read_bytes() returns those bytes completely unchanged — no interpretation at all.
- 10
- Asking Python to decode the same UTF-8 bytes as ASCII fails, because ASCII has no byte value above 127 and UTF-8's multi-byte sequences use them.
b'caf\xc3\xa9'
'café'
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)Why this works: é encodes to two UTF-8 bytes (\xc3\xa9), not one — read_bytes() shows those raw bytes untouched, and read_text(encoding="utf-8") correctly decodes them back to café. Asking for encoding="ascii" instead fails deterministically, because ASCII assigns no meaning to any byte above 127 — the exact byte UTF-8 used to represent é. This is the real failure encoding mismatches produce, not a hypothetical one.
Opening a genuinely binary file in text mode
Wrong
Better
What you see: UnicodeDecodeError raised almost immediately — a PNG's raw bytes are essentially never a valid UTF-8 byte sequence, so text mode's automatic decode step fails on the file's own header bytes.
Why: Text mode always tries to decode every byte it reads using the given encoding, whether or not the file is actually text. A binary format like PNG, ZIP, or a compiled executable has no encoding at all — it needs "rb" so Python returns the raw bytes untouched rather than attempting to interpret them as UTF-8 text.
- Text mode ("r"/"w")
- Decodes bytes to str on read, encodes str to bytes on write
- Needs an encoding — always pass encoding="utf-8" explicitly
- For files that ARE genuinely text: source code, JSON, CSV, logs
- Binary mode ("rb"/"wb")
- No decoding at all — you get and give raw bytes
- No encoding parameter — bytes have no encoding
- For files that are NOT text: images, zips, compiled binaries
Common mode strings
Together
Remember: The mode string decides str ("r") vs. bytes ("rb") — text mode always needs an explicit encoding (encoding="utf-8"), binary mode never takes one. A file that is not actually text must open "rb".
See also: pathlib module · streaming and large file processing

