Skip to content

_pyio buffered classes raise AttributeError after detach() instead of ValueError #160472

Closed as duplicate

Description

@wlkr73

Bug description

After detach(), the pure-Python buffered classes in _pyio raise AttributeError
from the None raw stream, where the C implementations raise ValueError. The two
implementations therefore disagree on the kind of error, not just the wording.

import io, _pyio

for mod in (io, _pyio):
    b = mod.BufferedReader(mod.BytesIO(b"abcdef"))
    b.detach()
    for op, args in [("read", (1,)), ("seek", (0,)), ("truncate", ()), ("flush", ())]:
        try:
            getattr(b, op)(*args)
            result = "no error"
        except Exception as exc:
            result = type(exc).__name__
        print(mod.__name__, op, "->", result)
io read -> ValueError
io seek -> ValueError
io truncate -> ValueError
io flush -> ValueError
_pyio read -> AttributeError
_pyio seek -> AttributeError
_pyio truncate -> AttributeError
_pyio flush -> AttributeError

Same shape for _pyio.BufferedWriter (write, flush, seek, tell),
_pyio.BufferedRandom and _pyio.TextIOWrapper.tell().

Cause

In Lib/_pyio.py, BufferedReader.detach() does

def detach(self):
    if self.raw is None:
        raise ValueError("raw stream already detached")
    self.flush()
    raw = self._raw
    self._raw = None
    return raw

so the class already knows the detached state, but only detach() itself checks
it. Every other method reaches self.raw and fails on the None, producing
AttributeError: 'NoneType' object has no attribute '...'.

Expected behaviour

ValueError, matching the C implementation and matching the documented state of
the object ("After the raw stream has been detached, the buffer is in an unusable
state"). The check C performs is the CHECK_ATTACHED macro in
Modules/_io/bufferedio.c.

Why it matters beyond error text

test_io runs the same test classes against both implementations (C and
Py variants in Lib/test/test_io/), so any test that asserts ValueError for a
detached operation cannot be shared today. A _checkDetached() helper next to the
existing _checkClosed() would let those tests be written once.

Scope

I have not opened a patch, following the guidance in #160467 that the expected
behaviour should be agreed on the issue first. If the ValueError above is the
direction a triager wants, I can prepare the fix plus a test in test_textio.py
and test_bufferedio.py that runs against both implementations.

Found by differential testing _io against _pyio over random operation
sequences.

CPython versions tested on

CPython main branch (3.16.0a1+)

Operating systems tested on

Linux

Activity

  1. cmaloney commented on Oct 23, 2026

    @cmaloney
    Contributor

    Please read through https://devguide.python.org/getting-started/ai-tools/ in particular the "Considerations for success" section.

    I'm going to close this as a duplicate of gh-160467. Both _pyio and _io should have matching behavior (so a fix in one should happen in both).

    In general _pyio is not at parity at the moment, I've actually requested permission to remove it python/steering-council#372.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions