Skip to content

gh-104231: Make str() and repr() always returning str, and bytes() -- bytes #112583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,14 @@ Other language changes
ASCII :class:`bytes` and :term:`bytes-like objects <bytes-like object>`.
(Contributed by Daniel Pope in :gh:`129349`.)

* :func:`str`, :func:`repr` and :func:`ascii` now always return an instance
of :class:`str`, even if the special methods ``__str__()`` or ``__repr__()``
return an instance of a subclass of :class:`!str`.
:func:`bytes` now always returns an instance of :class:`bytes`, even
if the special method ``__bytes__()`` returns an instance of a subclass of
:class:`!bytes`.
(Contributed by Serhiy Storchaka in :gh:`104231`.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, you should now move this text (and the addition below) to Doc/whatsnew/3.15.rst.


* Support ``\z`` as a synonym for ``\Z`` in :mod:`regular expressions <re>`.
It is interpreted unambiguously in many other regular expression engines,
unlike ``\Z``, which has subtly different behavior.
Expand Down Expand Up @@ -2442,6 +2450,15 @@ Limited C API changes
C API. Keep :c:func:`PySequence_Fast` in the limited C API.
(Contributed by Victor Stinner in :gh:`91417`.)

* :c:func:`PyObject_Str`, :c:func:`PyObject_Repr` and :c:func:`PyObject_ASCII`
now always return an instance of :class:`str`, even if the special methods
``__str__()`` or ``__repr__()`` return an instance of a subclass of
:class:`!str`.
:c:func:`PyObject_Bytes` now always returns an instance of :class:`bytes`,
even if the special method ``__bytes__()`` returns an instance of a subclass
of :class:`!bytes`.
(Contributed by Serhiy Storchaka in :gh:`104231`.)


Porting to Python 3.14
----------------------
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,7 @@ def __bytes__(self):
self.assertEqual(BytesSubclass(StrWithBytes(OtherBytesSubclass(b'abc'))),
BytesSubclass(b'abc'))
# Issue #24731
self.assertTypedEqual(bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc'))
self.assertTypedEqual(bytes(WithBytes(BytesSubclass(b'abc'))), b'abc')
self.assertTypedEqual(BytesSubclass(WithBytes(BytesSubclass(b'abc'))),
BytesSubclass(b'abc'))
self.assertTypedEqual(BytesSubclass(WithBytes(OtherBytesSubclass(b'abc'))),
Expand All @@ -1143,8 +1143,7 @@ def __bytes__(self):
self.assertTypedEqual(bytes(BytesWithBytes(b'abc')), b'abc')
self.assertTypedEqual(BytesSubclass(BytesWithBytes(b'abc')),
BytesSubclass(b'abc'))
self.assertTypedEqual(bytes(BytesWithBytes(BytesSubclass(b'abc'))),
BytesSubclass(b'abc'))
self.assertTypedEqual(bytes(BytesWithBytes(BytesSubclass(b'abc'))), b'abc')
self.assertTypedEqual(BytesSubclass(BytesWithBytes(BytesSubclass(b'abc'))),
BytesSubclass(b'abc'))
self.assertTypedEqual(BytesSubclass(BytesWithBytes(OtherBytesSubclass(b'abc'))),
Expand Down
12 changes: 6 additions & 6 deletions Lib/test/test_capi/test_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def test_object_str(self):
self.assertTypedEqual(object_str('\U0001f40d'), '\U0001f40d')
self.assertTypedEqual(object_str(StrSubclass('abc')), 'abc')
self.assertTypedEqual(object_str(WithStr('abc')), 'abc')
self.assertTypedEqual(object_str(WithStr(StrSubclass('abc'))), StrSubclass('abc'))
self.assertTypedEqual(object_str(WithStr(StrSubclass('abc'))), 'abc')
self.assertTypedEqual(object_str(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(object_str(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(object_str(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(object_str(NULL), '<NULL>')

def test_object_repr(self):
Expand All @@ -94,9 +94,9 @@ def test_object_repr(self):
self.assertTypedEqual(object_repr('\U0001f40d'), "'\U0001f40d'")
self.assertTypedEqual(object_repr(StrSubclass('abc')), "'abc'")
self.assertTypedEqual(object_repr(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(object_repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>')
self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>'))
self.assertTypedEqual(object_repr(WithRepr(StrSubclass('<\U0001f40d>'))), '<\U0001f40d>')
self.assertTypedEqual(object_repr(NULL), '<NULL>')

def test_object_ascii(self):
Expand All @@ -107,7 +107,7 @@ def test_object_ascii(self):
self.assertTypedEqual(object_ascii('\U0001f40d'), r"'\U0001f40d'")
self.assertTypedEqual(object_ascii(StrSubclass('abc')), "'abc'")
self.assertTypedEqual(object_ascii(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(object_ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>')
self.assertTypedEqual(object_ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>')
self.assertTypedEqual(object_ascii(NULL), '<NULL>')
Expand All @@ -119,7 +119,7 @@ def test_object_bytes(self):
self.assertTypedEqual(object_bytes(b'abc'), b'abc')
self.assertTypedEqual(object_bytes(BytesSubclass(b'abc')), b'abc')
self.assertTypedEqual(object_bytes(WithBytes(b'abc')), b'abc')
self.assertTypedEqual(object_bytes(WithBytes(BytesSubclass(b'abc'))), BytesSubclass(b'abc'))
self.assertTypedEqual(object_bytes(WithBytes(BytesSubclass(b'abc'))), b'abc')
self.assertTypedEqual(object_bytes(bytearray(b'abc')), b'abc')
self.assertTypedEqual(object_bytes(memoryview(b'abc')), b'abc')
self.assertTypedEqual(object_bytes([97, 98, 99]), b'abc')
Expand Down
12 changes: 6 additions & 6 deletions Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_ascii(self):
self.assertTypedEqual(ascii('\U0001f40d'), r"'\U0001f40d'")
self.assertTypedEqual(ascii(StrSubclass('abc')), "'abc'")
self.assertTypedEqual(ascii(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(ascii(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(ascii(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(ascii(WithRepr('<\U0001f40d>')), r'<\U0001f40d>')
self.assertTypedEqual(ascii(WithRepr(StrSubclass('<\U0001f40d>'))), r'<\U0001f40d>')
self.assertRaises(TypeError, ascii, WithRepr(b'byte-repr'))
Expand Down Expand Up @@ -194,9 +194,9 @@ def test_repr(self):
self.assertTypedEqual(repr('\U0001f40d'), "'\U0001f40d'")
self.assertTypedEqual(repr(StrSubclass('abc')), "'abc'")
self.assertTypedEqual(repr(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(repr(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(repr(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(repr(WithRepr('<\U0001f40d>')), '<\U0001f40d>')
self.assertTypedEqual(repr(WithRepr(StrSubclass('<\U0001f40d>'))), StrSubclass('<\U0001f40d>'))
self.assertTypedEqual(repr(WithRepr(StrSubclass('<\U0001f40d>'))), '<\U0001f40d>')
self.assertRaises(TypeError, repr, WithRepr(b'byte-repr'))

def test_iterators(self):
Expand Down Expand Up @@ -2415,23 +2415,23 @@ def __str__(self):
return self.value

self.assertTypedEqual(str(WithStr('abc')), 'abc')
self.assertTypedEqual(str(WithStr(StrSubclass('abc'))), StrSubclass('abc'))
self.assertTypedEqual(str(WithStr(StrSubclass('abc'))), 'abc')
self.assertTypedEqual(StrSubclass(WithStr('abc')), StrSubclass('abc'))
self.assertTypedEqual(StrSubclass(WithStr(StrSubclass('abc'))),
StrSubclass('abc'))
self.assertTypedEqual(StrSubclass(WithStr(OtherStrSubclass('abc'))),
StrSubclass('abc'))

self.assertTypedEqual(str(StrWithStr('abc')), 'abc')
self.assertTypedEqual(str(StrWithStr(StrSubclass('abc'))), StrSubclass('abc'))
self.assertTypedEqual(str(StrWithStr(StrSubclass('abc'))), 'abc')
self.assertTypedEqual(StrSubclass(StrWithStr('abc')), StrSubclass('abc'))
self.assertTypedEqual(StrSubclass(StrWithStr(StrSubclass('abc'))),
StrSubclass('abc'))
self.assertTypedEqual(StrSubclass(StrWithStr(OtherStrSubclass('abc'))),
StrSubclass('abc'))

self.assertTypedEqual(str(WithRepr('<abc>')), '<abc>')
self.assertTypedEqual(str(WithRepr(StrSubclass('<abc>'))), StrSubclass('<abc>'))
self.assertTypedEqual(str(WithRepr(StrSubclass('<abc>'))), '<abc>')
self.assertTypedEqual(StrSubclass(WithRepr('<abc>')), StrSubclass('<abc>'))
self.assertTypedEqual(StrSubclass(WithRepr(StrSubclass('<abc>'))),
StrSubclass('<abc>'))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:c:func:`PyObject_Str`, :c:func:`PyObject_Repr` and :c:func:`PyObject_ASCII`
now always return an instance of :class:`str`, even if the special methods
``__str__()`` or ``__repr__()`` return an instance of a subclass of
:class:`!str`.
:c:func:`PyObject_Bytes` now always returns an instance of :class:`bytes`, even
if the special method ``__bytes__()`` returns an instance of a subclass of
:class:`!bytes`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:func:`str`, :func:`repr` and :func:`ascii` now always return an instance
of :class:`str`, even if the special methods ``__str__()`` or ``__repr__()``
return an instance of a subclass of :class:`!str`.
:func:`bytes` now always returns an instance of :class:`bytes`, even
if the special method ``__bytes__()`` returns an instance of a subclass of
:class:`!bytes`.
19 changes: 13 additions & 6 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2786,12 +2786,19 @@ bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding,
Py_DECREF(func);
if (bytes == NULL)
return NULL;
if (!PyBytes_Check(bytes)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(bytes)->tp_name);
Py_DECREF(bytes);
return NULL;
if (!PyBytes_CheckExact(bytes)) {
if (!PyBytes_Check(bytes)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(bytes)->tp_name);
Py_DECREF(bytes);
return NULL;
}
Py_SETREF(bytes, PyBytes_FromStringAndSize(PyBytes_AS_STRING(bytes),
PyBytes_GET_SIZE(bytes)));
if (bytes == NULL) {
return NULL;
}
}
}
else if (PyErr_Occurred())
Expand Down
15 changes: 13 additions & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -779,8 +779,8 @@ PyObject_Repr(PyObject *v)
res = (*Py_TYPE(v)->tp_repr)(v);
_Py_LeaveRecursiveCallTstate(tstate);

if (res == NULL) {
return NULL;
if (res == NULL || PyUnicode_CheckExact(res)) {
return res;
}
if (!PyUnicode_Check(res)) {
_PyErr_Format(tstate, PyExc_TypeError,
Expand All @@ -789,6 +789,7 @@ PyObject_Repr(PyObject *v)
Py_DECREF(res);
return NULL;
}
Py_SETREF(res, _PyUnicode_Copy(res));
return res;
}

Expand Down Expand Up @@ -825,6 +826,10 @@ PyObject_Str(PyObject *v)
if (res == NULL) {
return NULL;
}
if (PyUnicode_CheckExact(res)) {
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
}
if (!PyUnicode_Check(res)) {
_PyErr_Format(tstate, PyExc_TypeError,
"__str__ returned non-string (type %.200s)",
Expand All @@ -833,6 +838,7 @@ PyObject_Str(PyObject *v)
return NULL;
}
assert(_PyUnicode_CheckConsistency(res, 1));
Py_SETREF(res, _PyUnicode_Copy(res));
return res;
}

Expand Down Expand Up @@ -881,13 +887,18 @@ PyObject_Bytes(PyObject *v)
Py_DECREF(func);
if (result == NULL)
return NULL;
if (PyBytes_CheckExact(result)) {
return result;
}
if (!PyBytes_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__bytes__ returned non-bytes (type %.200s)",
Py_TYPE(result)->tp_name);
Py_DECREF(result);
return NULL;
}
Py_SETREF(result, PyBytes_FromStringAndSize(PyBytes_AS_STRING(result),
PyBytes_GET_SIZE(result)));
return result;
}
else if (PyErr_Occurred())
Expand Down
Loading