GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `hasattr(stream, "buffer")` fails with ValueError if buffer is detached · Issue #160427 · python/cpython

hasattr(stream, "buffer") fails with ValueError if buffer is detached #160427

New issue
Copy link
New issue
Copy link
Closed
#160802
Closed
hasattr(stream, "buffer") fails with ValueError if buffer is detached#160427
#160802
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@pekkaklarck
pekkaklarck
opened on Oct 23, 2026
Issue body actions

Bug report

>>> stream  =  open("example.txt", "w")
>>> hasattr(stream, "buffer")
True
>>> stream.detach()
<_io.BufferedWriter name='example.txt'>
>>> hasattr(stream, "buffer")
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    hasattr(stream, "buffer")
    ~~~~~~~^^^^^^^^^^^^^^^^^^
ValueError: underlying buffer has been detached

This occurs with Python 3.14.9 and 3.15.1, but not with earlier versions such as 3.14.8 and 3.15.0. Tested both on Linux and on Windows. I consider hasattr failing with an exception pretty bad, especially when using it with standard library objects, and the behavior changing in a bug fix release makes it even worse.

I noticed this when a unit test for our custom isatty utility started to fail (robotframework/robotframework#5825). We have that utility so that we can safely check is a stream connected to console or not. It used to look like this:

def isatty(stream):
    # first check if buffer was detached
    if hasattr(stream, "buffer") and stream.buffer is None:
        return False
    if not hasattr(stream, "isatty"):
        return False
    try:
        return stream.isatty()
    except ValueError:  # Occurs if file is closed.
        return False

I already fixed this in our end by simply removing the separate detached buffer check. stream.isatty() raised a ValueError if its buffer is detached, similarly when the stream is closed, and we already handle that. Apparently the separate check was added before handling closed streams and nobody noticed earlier that the check is redundant.

CPython versions tested on:

3.14, 3.15

Operating systems tested on:

Linux, Windows

Linked PRs

  • gh-160427: Return None from TextIOWrapper.buffer once detached #160467
  • gh-160427: Return None from TextIOWrapper.buffer when detached #160802
  • [3.15] gh-160427: Return None from TextIOWrapper.buffer when detached (GH-160802) #161449
  • [3.14] gh-160427: Return None from TextIOWrapper.buffer when detached (GH-160802) #161450

Activity

  1. pekkaklarck
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Oct 23, 2026
  2. NekoAsakura
    added
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    topic-IO
    on Oct 23, 2026
  3. scrieve
    added a commit that references this issue on Oct 23, 2026

    pythongh-160427: Return None from TextIOWrapper.buffer once detached

    0071853
  4. scrieve commented on Oct 23, 2026

    @scrieve
    scrieve
    on Oct 23, 2026
    Contributor
    More actions

    I would like to settle the intended behaviour here before asking anyone to review a patch.

    The case for None:

    • In 3.14.8 buffer was a member descriptor (_Py_T_OBJECT), so a detached wrapper read as None and hasattr() did not raise.
    • _pyio.TextIOWrapper.buffer still returns None after detach, so the two implementations disagree today.
    • Nothing in the io docs says the attribute raises. The nearest statement is that the TextIOBase is unusable after detach, which reads as being about operations.

    The case for the current ValueError is that gh-157364 deliberately routed the attribute through buffer_access_safe() while closing a use-after-free. That guard is still needed for reads and writes; the open question is only whether the attribute getter is the right place to raise it.

    Proposal: the getter returns None when the wrapper is detached but initialised, and keeps raising ValueError for the uninitialised case. Every other caller keeps the existing guard, so nothing that gh-157364 protected changes.

    If a triager or developer would rather keep the ValueError, that is a fine answer and I will drop the patch. A patch against this proposal is at #160467, held as a draft until there is agreement here.

  5. cmaloney
    marked _pyio buffered classes raise AttributeError after detach() instead of ValueError #160472 as a duplicate of this issue on Oct 23, 2026
  6. cmaloney commented on Oct 24, 2026

    @cmaloney
    cmaloney
    on Oct 24, 2026
    Contributor
    More actions

    cc: @vstinner gh-157364 backports made an API change. Revert or I can work next week on a new PR to fix?

  7. pekkaklarck commented on Oct 24, 2026

    @pekkaklarck
    pekkaklarck
    on Oct 24, 2026
    Author
    More actions

    FWIW, as a user I'd expect hasattr (and isinstance and other such functions) to return a Boolean and never raise. I know this depends on the underlying type, but I'd expect this to hold at least with the standard library types.

  8. pekkaklarck
    changed the title [-]`hasattr(stream, "buffer") fails with ValueError if buffer is detached[/-] [+]`hasattr(stream, "buffer")` fails with ValueError if buffer is detached[/+] on Oct 28, 2026
  9. cmaloney commented on Oct 28, 2026

    @cmaloney
    cmaloney
    on Oct 28, 2026
    Contributor
    More actions

    I did an audit and there are quite a few attributes that raise after a change, such as memoryview attributes after .release(). I suspect moving to fully uniform behavior is out of question. That said, this definitely shouldn't have changed in a point release so working to restore the old behavior.

  10. cmaloney commented on Oct 28, 2026

    @cmaloney
    cmaloney
    on Oct 28, 2026
    Contributor
    More actions

    This actually broke in a free-threading change: gh-154523. I'm leaning always return None here where self->buffer is NULL which should backport easily. I think the None behavior from the descriptor is good to restore.

  11. cmaloney
    linked a pull request that will close this issuegh-160427: Return None from TextIOWrapper.buffer when detached #160802on Oct 28, 2026
  12. cmaloney
    added a commit that references this issue on Nov 5, 2026

    gh-160427: Return None from TextIOWrapper.buffer when detached (#160802)

    3422197
  13. cmaloney
    closed this as completedin #160802on Nov 5, 2026
  14. cmaloney
    added 2 commits that reference this issue on Nov 5, 2026

    [3.15] gh-160427: Return None from TextIOWrapper.buffer when detached (…

    4af98bd

    [3.14] gh-160427: Return None from TextIOWrapper.buffer when detached (…

    9106913
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

    extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-IOtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.