python/cpython · error · ValueError
can't have unbuffered text I/O
Error message
can't have unbuffered text I/O
What it means
Raised by io.open() as a ValueError when buffering == 0 is requested in text mode. True unbuffered I/O only exists at the byte level; TextIOWrapper fundamentally needs a buffer to decode multi-byte encodings and translate newlines, so buffering=0 is rejected for any mode containing 't' (or plain 'r'/'w'/'a').
Source
Thrown at Lib/_pyio.py:253
(reading and "r" or "") +
(writing and "w" or "") +
(appending and "a" or "") +
(updating and "+" or ""),
closefd, opener=opener)
result = raw
try:
line_buffering = False
if buffering == 1 or buffering < 0 and raw._isatty_open_only():
buffering = -1
line_buffering = True
if buffering < 0:
buffering = max(min(raw._blksize, 8192 * 1024), DEFAULT_BUFFER_SIZE)
if buffering < 0:
raise ValueError("invalid buffering size")
if buffering == 0:
if binary:
return result
raise ValueError("can't have unbuffered text I/O")
if updating:
buffer = BufferedRandom(raw, buffering)
elif creating or writing or appending:
buffer = BufferedWriter(raw, buffering)
elif reading:
buffer = BufferedReader(raw, buffering)
else:
raise ValueError("unknown mode: %r" % mode)
result = buffer
if binary:
return result
encoding = text_encoding(encoding)
text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
result = text
text.mode = mode
return result
except:
result.close()View on GitHub (pinned to bc6749cc3b)
Solutions
- For text mode, use line buffering (buffering=1) or write + explicit f.flush() after each record.
- Switch to binary mode ('wb') if you truly need unbuffered writes and can work with bytes.
- For logs that must survive crashes, flush() plus os.fsync(f.fileno()) rather than buffering=0.
Example fix
// before
with open('app.log', 'w', buffering=0) as f: # ValueError: can't have unbuffered text I/O
f.write(line)
// after
with open('app.log', 'w', buffering=1) as f: # line buffered text I/O
f.write(line + '\n')
# durability variant: f.flush(); os.fsync(f.fileno()) Defensive patterns
Strategy: validation
Validate before calling
def open_text(path, mode='w', unbuffered=False):
buffering = 1 if unbuffered else -1 # 0 is illegal for text
return open(path, mode, buffering=buffering, encoding='utf-8') Type guard
def buffering_legal(mode: str, buffering: int) -> bool:
return not (buffering == 0 and 'b' not in mode) Prevention
- Use buffering=1 (line buffered) plus newline-terminated writes for 'immediate' text output.
- Call f.flush() (and os.fsync for durability) instead of buffering=0 in text mode.
- Reserve buffering=0 for binary mode only.
When it happens
Trigger: open('f.txt', 'w', buffering=0) or open('f', 'r', buffering=0). Also wrappers that accept an unbuffered=True flag and map it to buffering=0 regardless of mode. (In binary mode buffering=0 is legal and returns the raw FileIO.)
Common situations: Log writers wanting every write to hit disk immediately; porting C stdbuf/setvbuf-style unbuffered expectations to Python text files; performance-tuning cargo cult that sets buffering=0 globally.
Related errors
- can't have text and binary mode at once
- invalid buffering size
- invalid buffering: %r
- invalid encoding: %r
- invalid errors: %r
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/bbb21f22261d30b2.
Report an issue: GitHub.