From 974cc01bf7c5ed37fdfac3c6ec31c4aae2c80f6e Mon Sep 17 00:00:00 2001 From: task Date: Sun, 7 Sep 2025 13:43:46 +0300 Subject: [PATCH 1/8] allocate a bytes object directly instead and manually construct a writable memoryview pointing to it. --- Modules/_io/iobase.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index aa373f6fdcb9d9..4a2b93834df7be 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -927,13 +927,19 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n) return PyObject_CallMethodNoArgs(self, &_Py_ID(readall)); } - /* TODO: allocate a bytes object directly instead and manually construct - a writable memoryview pointing to it. */ - b = PyByteArray_FromStringAndSize(NULL, n); + b = PyBytes_FromStringAndSize(NULL, n); if (b == NULL) return NULL; - res = PyObject_CallMethodObjArgs(self, &_Py_ID(readinto), b, NULL); + PyObject *mv = PyMemoryView_FromMemory(PyBytes_AS_STRING(b), n, PyBUF_WRITE); + if (mv == NULL) { + Py_DECREF(b); + return NULL; + } + + res = PyObject_CallMethodObjArgs(self, &_Py_ID(readinto), mv, NULL); + Py_DECREF(mv); + if (res == NULL || res == Py_None) { Py_DECREF(b); return res; @@ -946,9 +952,14 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n) return NULL; } - res = PyBytes_FromStringAndSize(PyByteArray_AsString(b), n); - Py_DECREF(b); - return res; + if (n != PyBytes_GET_SIZE(b)) { + if (_PyBytes_Resize(&b, n) < 0) { + Py_DECREF(b); + return NULL; + } + } + + return b; } From b44bec975e03820d4d45efe98b084869f8ae98ba Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 7 Sep 2025 10:52:10 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst diff --git a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst new file mode 100644 index 00000000000000..00b0dc4e02e2a8 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst @@ -0,0 +1 @@ +Read directly to bytes object From 897e8134ce2cf1e56317534898b92f8424bcf879 Mon Sep 17 00:00:00 2001 From: task Date: Sun, 7 Sep 2025 14:17:18 +0300 Subject: [PATCH 3/8] gh-60107: gh-138616: Minor code changes --- Modules/_io/iobase.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index 4a2b93834df7be..7cb606cc70823e 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -921,7 +921,7 @@ static PyObject * _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n) /*[clinic end generated code: output=6cdeb731e3c9f13c input=b6d0dcf6417d1374]*/ { - PyObject *b, *res; + PyObject *b, *res, *mv; if (n < 0) { return PyObject_CallMethodNoArgs(self, &_Py_ID(readall)); @@ -931,7 +931,7 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n) if (b == NULL) return NULL; - PyObject *mv = PyMemoryView_FromMemory(PyBytes_AS_STRING(b), n, PyBUF_WRITE); + mv = PyMemoryView_FromMemory(PyBytes_AS_STRING(b), n, PyBUF_WRITE); if (mv == NULL) { Py_DECREF(b); return NULL; @@ -952,11 +952,9 @@ _io__RawIOBase_read_impl(PyObject *self, Py_ssize_t n) return NULL; } - if (n != PyBytes_GET_SIZE(b)) { - if (_PyBytes_Resize(&b, n) < 0) { - Py_DECREF(b); - return NULL; - } + if (_PyBytes_Resize(&b, n) < 0) { + Py_DECREF(b); + return NULL; } return b; From 2351f34332118d3413750644cccc8ba79dbc6bf9 Mon Sep 17 00:00:00 2001 From: task Date: Sun, 7 Sep 2025 14:25:39 +0300 Subject: [PATCH 4/8] gh-60107: Change blurb_it --- .../next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst index 00b0dc4e02e2a8..1fb4e6174c0867 100644 --- a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst +++ b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst @@ -1 +1 @@ -Read directly to bytes object +* gh-60107: avoid io.RawIOBase.read from reading into a temporary bytearray (author: taskevich) \ No newline at end of file From c67112234db920771be2a3f5048aa888228b291d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9E=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=B1=D1=83=D1=80=D1=86=D0=B5=D0=B2?= <93867297+taskevich@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:28:32 +0300 Subject: [PATCH 5/8] Update Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst index 1fb4e6174c0867..e2c9897f9b2c3e 100644 --- a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst +++ b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst @@ -1 +1,2 @@ -* gh-60107: avoid io.RawIOBase.read from reading into a temporary bytearray (author: taskevich) \ No newline at end of file +Avoid :meth:`io.RawIOBase.read` from reading into a temporary :class:`bytearray` +when calling its own :meth:`io.RawIOBase.readinto` internally. \ No newline at end of file From 5396b1c50d8b529098b369889f353e0d6590cb8b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 7 Sep 2025 11:30:54 +0000 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-09-07-11-30-53.gh-issue-60107.TFrtr-.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-07-11-30-53.gh-issue-60107.TFrtr-.rst diff --git a/Misc/NEWS.d/next/Library/2025-09-07-11-30-53.gh-issue-60107.TFrtr-.rst b/Misc/NEWS.d/next/Library/2025-09-07-11-30-53.gh-issue-60107.TFrtr-.rst new file mode 100644 index 00000000000000..57be38e5420633 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-07-11-30-53.gh-issue-60107.TFrtr-.rst @@ -0,0 +1,2 @@ +Avoid :meth:`io.RawIOBase.read` from reading into a temporary :class:`bytearray` +when calling its own :meth:`io.RawIOBase.readinto` internally. From 23cd0ff68f5b1f79c1ecea1ec6a26eae291aa54c Mon Sep 17 00:00:00 2001 From: task Date: Sun, 7 Sep 2025 14:37:56 +0300 Subject: [PATCH 7/8] Deleted incorrect news from C_API --- .../next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst diff --git a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst b/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst deleted file mode 100644 index e2c9897f9b2c3e..00000000000000 --- a/Misc/NEWS.d/next/C_API/2025-09-07-10-52-09.gh-issue-60107.dhcb2Y.rst +++ /dev/null @@ -1,2 +0,0 @@ -Avoid :meth:`io.RawIOBase.read` from reading into a temporary :class:`bytearray` -when calling its own :meth:`io.RawIOBase.readinto` internally. \ No newline at end of file From 905b3ec812e6b360514a7e0e71b2aca03a227c32 Mon Sep 17 00:00:00 2001 From: task Date: Sun, 7 Sep 2025 17:10:36 +0300 Subject: [PATCH 8/8] gh-60107: Tests for readinto --- Lib/test/test_io/test_general.py | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py index 604b56cea21fac..7c9986462da2bf 100644 --- a/Lib/test/test_io/test_general.py +++ b/Lib/test/test_io/test_general.py @@ -921,6 +921,49 @@ def test_RawIOBase_read(self): self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + def test_exact_RawIOBase(self): + rawio = self.MockRawIOWithoutRead((b"ab", b"cd")) + buf = bytearray(2) + n = rawio.readinto(buf) + self.assertEqual(n, 2) + self.assertEqual(buf, b"ab") + + n = rawio.readinto(buf) + self.assertEqual(n, 2) + self.assertEqual(buf, b"cd") + + n = rawio.readinto(buf) + self.assertEqual(n, 0) + self.assertEqual(buf, b"cd") + + def test_partial_readinto_write_RawIOBase(self): + rawio = self.MockRawIOWithoutRead((b"abcdef",)) + buf = bytearray(3) + n = rawio.readinto(buf) + self.assertEqual(n, 3) + self.assertEqual(buf, b"abc") + + n2 = rawio.readinto(buf) + self.assertEqual(n2, 3) + self.assertEqual(buf, b"def") + + def test_readinto_none_RawIOBase(self): + rawio = self.MockRawIOWithoutRead((None, b"x")) + buf = bytearray(2) + n = rawio.readinto(buf) + self.assertIsNone(n) + + n2 = rawio.readinto(buf) + self.assertEqual(n2, 1) + self.assertEqual(buf[0], ord('x')) + + def test_read_default_via_readinto_RawIOBase(self): + rawio = self.MockRawIOWithoutRead((b"abcdef",)) + result = rawio.read(4) + self.assertEqual(result, b"abcd") + result2 = rawio.read(4) + self.assertEqual(result2, b"ef") + def test_types_have_dict(self): test = ( self.IOBase(),