diff --git a/Doc/library/io.rst b/Doc/library/io.rst index 4da6e095d17..da7a681fa7c 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -477,7 +477,7 @@ I/O Base Classes A :exc:`BlockingIOError` is raised if the underlying raw stream is in non blocking-mode, and has no data available at the moment. - .. method:: read1(size=-1) + .. method:: read1([size]) Read and return up to *size* bytes, with at most one call to the underlying raw stream's :meth:`~RawIOBase.read` (or @@ -485,6 +485,9 @@ I/O Base Classes implementing your own buffering on top of a :class:`BufferedIOBase` object. + If *size* is −1 (the default), an arbitrary number of bytes are + returned (more than zero unless EOF is reached). + .. method:: readinto(b) Read bytes into a pre-allocated, writable @@ -628,13 +631,16 @@ than raw I/O does. Return :class:`bytes` containing the entire contents of the buffer. - .. method:: read1() + .. method:: read1([size]) - In :class:`BytesIO`, this is the same as :meth:`read`. + In :class:`BytesIO`, this is the same as :meth:`~BufferedIOBase.read`. - .. method:: readinto1() + .. versionchanged:: 3.7 + The *size* argument is now optional. - In :class:`BytesIO`, this is the same as :meth:`readinto`. + .. method:: readinto1(b) + + In :class:`BytesIO`, this is the same as :meth:`~BufferedIOBase.readinto`. .. versionadded:: 3.5 @@ -664,12 +670,15 @@ than raw I/O does. Read and return *size* bytes, or if *size* is not given or negative, until EOF or if the read call would block in non-blocking mode. - .. method:: read1(size) + .. method:: read1([size]) Read and return up to *size* bytes with only one call on the raw stream. If at least one byte is buffered, only buffered bytes are returned. Otherwise, one raw stream read call is made. + .. versionchanged:: 3.7 + The *size* argument is now optional. + .. class:: BufferedWriter(raw, buffer_size=DEFAULT_BUFFER_SIZE) diff --git a/Lib/_pyio.py b/Lib/_pyio.py index d0947f06d51..569527bc379 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -635,7 +635,7 @@ class BufferedIOBase(IOBase): implementation, but wrap one. """ - def read(self, size=None): + def read(self, size=-1): """Read and return up to size bytes, where size is an int. If the argument is omitted, None, or negative, reads and @@ -655,7 +655,7 @@ class BufferedIOBase(IOBase): """ self._unsupported("read") - def read1(self, size=None): + def read1(self, size=-1): """Read up to size bytes with at most one read() system call, where size is an int. """ @@ -863,7 +863,7 @@ class BytesIO(BufferedIOBase): self._buffer.clear() super().close() - def read(self, size=None): + def read(self, size=-1): if self.closed: raise ValueError("read from closed file") if size is None: @@ -877,7 +877,7 @@ class BytesIO(BufferedIOBase): self._pos = newpos return bytes(b) - def read1(self, size): + def read1(self, size=-1): """This is the same as read. """ return self.read(size) @@ -1073,12 +1073,12 @@ class BufferedReader(_BufferedIOMixin): self._read_pos = 0 return self._read_buf[self._read_pos:] - def read1(self, size): + def read1(self, size=-1): """Reads up to size bytes, with at most one read() system call.""" # Returns up to size bytes. If at least one byte is buffered, we # only return buffered bytes. Otherwise, we do one raw read. if size < 0: - raise ValueError("number of bytes to read must be positive") + size = self.buffer_size if size == 0: return b"" with self._read_lock: @@ -1270,7 +1270,7 @@ class BufferedRWPair(BufferedIOBase): self.reader = BufferedReader(reader, buffer_size) self.writer = BufferedWriter(writer, buffer_size) - def read(self, size=None): + def read(self, size=-1): if size is None: size = -1 return self.reader.read(size) @@ -1284,7 +1284,7 @@ class BufferedRWPair(BufferedIOBase): def peek(self, size=0): return self.reader.peek(size) - def read1(self, size): + def read1(self, size=-1): return self.reader.read1(size) def readinto1(self, b): @@ -1370,7 +1370,7 @@ class BufferedRandom(BufferedWriter, BufferedReader): self.flush() return BufferedReader.peek(self, size) - def read1(self, size): + def read1(self, size=-1): self.flush() return BufferedReader.read1(self, size) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 8a2111cbd71..877d3b5d97d 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1146,6 +1146,7 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEqual(b"a", bufio.read(1)) self.assertEqual(b"b", bufio.read1(1)) self.assertEqual(rawio._reads, 1) + self.assertEqual(b"", bufio.read1(0)) self.assertEqual(b"c", bufio.read1(100)) self.assertEqual(rawio._reads, 1) self.assertEqual(b"d", bufio.read1(100)) @@ -1154,8 +1155,17 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEqual(rawio._reads, 3) self.assertEqual(b"", bufio.read1(100)) self.assertEqual(rawio._reads, 4) - # Invalid args - self.assertRaises(ValueError, bufio.read1, -1) + + def test_read1_arbitrary(self): + rawio = self.MockRawIO((b"abc", b"d", b"efg")) + bufio = self.tp(rawio) + self.assertEqual(b"a", bufio.read(1)) + self.assertEqual(b"bc", bufio.read1()) + self.assertEqual(b"d", bufio.read1()) + self.assertEqual(b"efg", bufio.read1(-1)) + self.assertEqual(rawio._reads, 3) + self.assertEqual(b"", bufio.read1()) + self.assertEqual(rawio._reads, 4) def test_readinto(self): rawio = self.MockRawIO((b"abc", b"d", b"efg")) @@ -1806,6 +1816,7 @@ class BufferedRWPairTest(unittest.TestCase): pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) self.assertEqual(pair.read1(3), b"abc") + self.assertEqual(pair.read1(), b"def") def test_readinto(self): for method in ("readinto", "readinto1"): @@ -3467,6 +3478,7 @@ class MiscIOTest(unittest.TestCase): self.assertRaises(ValueError, f.read) if hasattr(f, "read1"): self.assertRaises(ValueError, f.read1, 1024) + self.assertRaises(ValueError, f.read1) if hasattr(f, "readall"): self.assertRaises(ValueError, f.readall) if hasattr(f, "readinto"): diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index 55b693e5647..80055ce1e7d 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -437,10 +437,8 @@ class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase): def test_read1(self): buf = self.buftype("1234567890") - memio = self.ioclass(buf) - - self.assertRaises(TypeError, memio.read1) - self.assertEqual(memio.read(), buf) + self.assertEqual(self.ioclass(buf).read1(), buf) + self.assertEqual(self.ioclass(buf).read1(-1), buf) def test_readinto(self): buf = self.buftype("1234567890") diff --git a/Misc/NEWS b/Misc/NEWS index 12ef2ceb61d..756b771f149 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -88,6 +88,10 @@ Core and Builtins Library ------- +- Issue #23214: In the "io" module, the argument to BufferedReader and + BytesIO's read1() methods is now optional and can be -1, matching the + BufferedIOBase specification. + - Issue #28480: Fix error building socket module when multithreading is disabled. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index cbe7425eaef..c760522616a 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -904,7 +904,7 @@ _io__Buffered_read_impl(buffered *self, Py_ssize_t n) CHECK_INITIALIZED(self) if (n < -1) { PyErr_SetString(PyExc_ValueError, - "read length must be positive or -1"); + "read length must be non-negative or -1"); return NULL; } @@ -932,22 +932,20 @@ _io__Buffered_read_impl(buffered *self, Py_ssize_t n) /*[clinic input] _io._Buffered.read1 - size as n: Py_ssize_t + size as n: Py_ssize_t = -1 / [clinic start generated code]*/ static PyObject * _io__Buffered_read1_impl(buffered *self, Py_ssize_t n) -/*[clinic end generated code: output=bcc4fb4e54d103a3 input=8d2869c18b983184]*/ +/*[clinic end generated code: output=bcc4fb4e54d103a3 input=7d22de9630b61774]*/ { Py_ssize_t have, r; PyObject *res = NULL; CHECK_INITIALIZED(self) if (n < 0) { - PyErr_SetString(PyExc_ValueError, - "read length must be positive"); - return NULL; + n = self->buffer_size; } CHECK_CLOSED(self, "read of closed file") diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index a1ba121e262..96be0f45413 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -420,7 +420,7 @@ _io_BytesIO_read_impl(bytesio *self, PyObject *arg) /*[clinic input] _io.BytesIO.read1 - size: object + size: object(c_default="Py_None") = -1 / Read at most size bytes, returned as a bytes object. @@ -430,8 +430,8 @@ Return an empty bytes object at EOF. [clinic start generated code]*/ static PyObject * -_io_BytesIO_read1(bytesio *self, PyObject *size) -/*[clinic end generated code: output=16021f5d0ac3d4e2 input=d4f40bb8f2f99418]*/ +_io_BytesIO_read1_impl(bytesio *self, PyObject *size) +/*[clinic end generated code: output=a60d80c84c81a6b8 input=0951874bafee8e80]*/ { return _io_BytesIO_read_impl(self, size); } diff --git a/Modules/_io/clinic/bufferedio.c.h b/Modules/_io/clinic/bufferedio.c.h index 58144a40154..dc69c48557d 100644 --- a/Modules/_io/clinic/bufferedio.c.h +++ b/Modules/_io/clinic/bufferedio.c.h @@ -140,23 +140,24 @@ exit: } PyDoc_STRVAR(_io__Buffered_read1__doc__, -"read1($self, size, /)\n" +"read1($self, size=-1, /)\n" "--\n" "\n"); #define _IO__BUFFERED_READ1_METHODDEF \ - {"read1", (PyCFunction)_io__Buffered_read1, METH_O, _io__Buffered_read1__doc__}, + {"read1", (PyCFunction)_io__Buffered_read1, METH_VARARGS, _io__Buffered_read1__doc__}, static PyObject * _io__Buffered_read1_impl(buffered *self, Py_ssize_t n); static PyObject * -_io__Buffered_read1(buffered *self, PyObject *arg) +_io__Buffered_read1(buffered *self, PyObject *args) { PyObject *return_value = NULL; - Py_ssize_t n; + Py_ssize_t n = -1; - if (!PyArg_Parse(arg, "n:read1", &n)) { + if (!PyArg_ParseTuple(args, "|n:read1", + &n)) { goto exit; } return_value = _io__Buffered_read1_impl(self, n); @@ -475,4 +476,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=a956f394ecde4cf9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=490c97bfcfd92c51 input=a9049054013a1b77]*/ diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h index c64ce5c77c3..1434782925e 100644 --- a/Modules/_io/clinic/bytesio.c.h +++ b/Modules/_io/clinic/bytesio.c.h @@ -181,7 +181,7 @@ exit: } PyDoc_STRVAR(_io_BytesIO_read1__doc__, -"read1($self, size, /)\n" +"read1($self, size=-1, /)\n" "--\n" "\n" "Read at most size bytes, returned as a bytes object.\n" @@ -190,7 +190,27 @@ PyDoc_STRVAR(_io_BytesIO_read1__doc__, "Return an empty bytes object at EOF."); #define _IO_BYTESIO_READ1_METHODDEF \ - {"read1", (PyCFunction)_io_BytesIO_read1, METH_O, _io_BytesIO_read1__doc__}, + {"read1", (PyCFunction)_io_BytesIO_read1, METH_VARARGS, _io_BytesIO_read1__doc__}, + +static PyObject * +_io_BytesIO_read1_impl(bytesio *self, PyObject *size); + +static PyObject * +_io_BytesIO_read1(bytesio *self, PyObject *args) +{ + PyObject *return_value = NULL; + PyObject *size = Py_None; + + if (!PyArg_UnpackTuple(args, "read1", + 0, 1, + &size)) { + goto exit; + } + return_value = _io_BytesIO_read1_impl(self, size); + +exit: + return return_value; +} PyDoc_STRVAR(_io_BytesIO_readline__doc__, "readline($self, size=None, /)\n" @@ -428,4 +448,4 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=6382e8eb578eea64 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8f469431da1b3857 input=a9049054013a1b77]*/