You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
hasattr(stream, "buffer") fails with ValueError if buffer is detached #160427
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:
defisatty(stream):
# first check if buffer was detachedifhasattr(stream, "buffer") andstream.bufferisNone:
returnFalseifnothasattr(stream, "isatty"):
returnFalsetry:
returnstream.isatty()
exceptValueError: # Occurs if file is closed.returnFalse
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.
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.
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.
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
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.
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.
Activity
pythongh-160427: Return None from TextIOWrapper.buffer once detached
scrieve commentedon Oct 23, 2026
I would like to settle the intended behaviour here before asking anyone to review a patch.
The case for
None:bufferwas a member descriptor (_Py_T_OBJECT), so a detached wrapper read asNoneandhasattr()did not raise._pyio.TextIOWrapper.bufferstill returnsNoneafter detach, so the two implementations disagree today.iodocs says the attribute raises. The nearest statement is that theTextIOBaseis unusable after detach, which reads as being about operations.The case for the current
ValueErroris that gh-157364 deliberately routed the attribute throughbuffer_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
Nonewhen the wrapper is detached but initialised, and keeps raisingValueErrorfor 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.cmaloney commentedon Oct 24, 2026
cc: @vstinner gh-157364 backports made an API change. Revert or I can work next week on a new PR to fix?
pekkaklarck commentedon Oct 24, 2026
FWIW, as a user I'd expect
hasattr(andisinstanceand 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.[-]`hasattr(stream, "buffer") fails with ValueError if buffer is detached[/-][+]`hasattr(stream, "buffer")` fails with ValueError if buffer is detached[/+]cmaloney commentedon Oct 28, 2026
I did an audit and there are quite a few attributes that raise after a change, such as
memoryviewattributes 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.cmaloney commentedon Oct 28, 2026
This actually broke in a free-threading change: gh-154523. I'm leaning always return None here where
self->bufferis NULL which should backport easily. I think theNonebehavior from the descriptor is good to restore.