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

  1. For text mode, use line buffering (buffering=1) or write + explicit f.flush() after each record.
  2. Switch to binary mode ('wb') if you truly need unbuffered writes and can work with bytes.
  3. 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

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


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/bbb21f22261d30b2. Report an issue: GitHub.