Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 10e847b

Browse files
committed
add BufferedIOBase.readinto1 (closes #20578)
Patch by Nikolaus Rath.
1 parent 66f80e9 commit 10e847b

5 files changed

Lines changed: 194 additions & 14 deletions

File tree

Doc/library/io.rst

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@ I/O Base Classes
385385
.. method:: readinto(b)
386386

387387
Read up to ``len(b)`` bytes into :class:`bytearray` *b* and return the
388-
number of bytes read. If the object is in non-blocking mode and no
389-
bytes are available, ``None`` is returned.
388+
number of bytes read. If the object is in non-blocking mode and no bytes
389+
are available, ``None`` is returned.
390390

391391
.. method:: write(b)
392392

@@ -459,10 +459,11 @@ I/O Base Classes
459459

460460
.. method:: read1(size=-1)
461461

462-
Read and return up to *size* bytes, with at most one call to the underlying
463-
raw stream's :meth:`~RawIOBase.read` method. This can be useful if you
464-
are implementing your own buffering on top of a :class:`BufferedIOBase`
465-
object.
462+
Read and return up to *size* bytes, with at most one call to the
463+
underlying raw stream's :meth:`~RawIOBase.read` (or
464+
:meth:`~RawIOBase.readinto`) method. This can be useful if you
465+
are implementing your own buffering on top of a
466+
:class:`BufferedIOBase` object.
466467

467468
.. method:: readinto(b)
468469

@@ -472,8 +473,19 @@ I/O Base Classes
472473
Like :meth:`read`, multiple reads may be issued to the underlying raw
473474
stream, unless the latter is interactive.
474475

476+
A :exc:`BlockingIOError` is raised if the underlying raw stream is in non
477+
blocking-mode, and has no data available at the moment.
478+
479+
.. method:: readinto1(b)
480+
481+
Read up to ``len(b)`` bytes into bytearray *b*, using at most one call to
482+
the underlying raw stream's :meth:`~RawIOBase.read` (or
483+
:meth:`~RawIOBase.readinto`) method. Return the number of bytes read.
484+
475485
A :exc:`BlockingIOError` is raised if the underlying raw stream is in
476-
non blocking-mode, and has no data available at the moment.
486+
non-blocking mode and has no data available at the moment.
487+
488+
.. versionadded:: 3.5
477489

478490
.. method:: write(b)
479491

@@ -590,6 +602,11 @@ than raw I/O does.
590602

591603
In :class:`BytesIO`, this is the same as :meth:`read`.
592604

605+
.. method:: readinto1()
606+
607+
In :class:`BytesIO`, this is the same as :meth:`readinto`.
608+
609+
.. versionadded:: 3.5
593610

594611
.. class:: BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE)
595612

Lib/_pyio.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,8 +655,26 @@ def readinto(self, b):
655655
Raises BlockingIOError if the underlying raw stream has no
656656
data at the moment.
657657
"""
658+
659+
return self._readinto(b, read1=False)
660+
661+
def readinto1(self, b):
662+
"""Read up to len(b) bytes into *b*, using at most one system call
663+
664+
Returns an int representing the number of bytes read (0 for EOF).
665+
666+
Raises BlockingIOError if the underlying raw stream has no
667+
data at the moment.
668+
"""
669+
670+
return self._readinto(b, read1=True)
671+
672+
def _readinto(self, b, read1):
658673
# XXX This ought to work with anything that supports the buffer API
659-
data = self.read(len(b))
674+
if read1:
675+
data = self.read1(len(b))
676+
else:
677+
data = self.read(len(b))
660678
n = len(data)
661679
try:
662680
b[:n] = data
@@ -1058,6 +1076,62 @@ def read1(self, size):
10581076
return self._read_unlocked(
10591077
min(size, len(self._read_buf) - self._read_pos))
10601078

1079+
# Implementing readinto() and readinto1() is not strictly necessary (we
1080+
# could rely on the base class that provides an implementation in terms of
1081+
# read() and read1()). We do ai anyway to keep the _pyio implementation
1082+
# similar to the io implementation (which implements the methods for
1083+
# performance reasons).
1084+
def readinto(self, buf):
1085+
"""Read data into *buf*."""
1086+
return self._readinto(buf, read1=False)
1087+
def readinto1(self, buf):
1088+
"""Read data into *buf* with at most one system call."""
1089+
return self._readinto(buf, read1=True)
1090+
1091+
def _readinto(self, buf, read1):
1092+
"""Read data into *buf* with at most one system call."""
1093+
1094+
if len(buf) == 0:
1095+
return 0
1096+
1097+
written = 0
1098+
with self._read_lock:
1099+
while written < len(buf):
1100+
1101+
# First try to read from internal buffer
1102+
avail = min(len(self._read_buf) - self._read_pos, len(buf))
1103+
if avail:
1104+
buf[written:written+avail] = \
1105+
self._read_buf[self._read_pos:self._read_pos+avail]
1106+
self._read_pos += avail
1107+
written += avail
1108+
if written == len(buf):
1109+
break
1110+
1111+
# If remaining space in callers buffer is larger than
1112+
# internal buffer, read directly into callers buffer
1113+
if len(buf) - written > self.buffer_size:
1114+
# If we don't use a memoryview, slicing buf will create
1115+
# a new object
1116+
if not isinstance(buf, memoryview):
1117+
buf = memoryview(buf)
1118+
n = self.raw.readinto(buf[written:])
1119+
if not n:
1120+
break # eof
1121+
written += n
1122+
1123+
# Otherwise refill internal buffer - unless we're
1124+
# in read1 mode and already got some data
1125+
elif not (read1 and written):
1126+
if not self._peek_unlocked(1):
1127+
break # eof
1128+
1129+
# In readinto1 mode, return as soon as we have some data
1130+
if read1 and written:
1131+
break
1132+
1133+
return written
1134+
10611135
def tell(self):
10621136
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
10631137

@@ -1207,6 +1281,9 @@ def peek(self, size=0):
12071281
def read1(self, size):
12081282
return self.reader.read1(size)
12091283

1284+
def readinto1(self, b):
1285+
return self.reader.readinto1(b)
1286+
12101287
def readable(self):
12111288
return self.reader.readable()
12121289

@@ -1289,6 +1366,10 @@ def read1(self, size):
12891366
self.flush()
12901367
return BufferedReader.read1(self, size)
12911368

1369+
def readinto1(self, b):
1370+
self.flush()
1371+
return BufferedReader.readinto1(self, b)
1372+
12921373
def write(self, b):
12931374
if self._read_buf:
12941375
# Undo readahead

Lib/test/test_io.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,29 @@ def test_readinto(self):
911911
self.assertEqual(bufio.readinto(b), 1)
912912
self.assertEqual(b, b"cb")
913913

914+
def test_readinto1(self):
915+
buffer_size = 10
916+
rawio = self.MockRawIO((b"abc", b"de", b"fgh", b"jkl"))
917+
bufio = self.tp(rawio, buffer_size=buffer_size)
918+
b = bytearray(2)
919+
self.assertEqual(bufio.peek(3), b'abc')
920+
self.assertEqual(rawio._reads, 1)
921+
self.assertEqual(bufio.readinto1(b), 2)
922+
self.assertEqual(b, b"ab")
923+
self.assertEqual(rawio._reads, 1)
924+
self.assertEqual(bufio.readinto1(b), 1)
925+
self.assertEqual(b[:1], b"c")
926+
self.assertEqual(rawio._reads, 1)
927+
self.assertEqual(bufio.readinto1(b), 2)
928+
self.assertEqual(b, b"de")
929+
self.assertEqual(rawio._reads, 2)
930+
b = bytearray(2*buffer_size)
931+
self.assertEqual(bufio.peek(3), b'fgh')
932+
self.assertEqual(rawio._reads, 3)
933+
self.assertEqual(bufio.readinto1(b), 6)
934+
self.assertEqual(b[:6], b"fghjkl")
935+
self.assertEqual(rawio._reads, 4)
936+
914937
def test_readlines(self):
915938
def bufio():
916939
rawio = self.MockRawIO((b"abc\n", b"d\n", b"ef"))
@@ -2985,6 +3008,8 @@ def test_io_after_close(self):
29853008
self.assertRaises(ValueError, f.readall)
29863009
if hasattr(f, "readinto"):
29873010
self.assertRaises(ValueError, f.readinto, bytearray(1024))
3011+
if hasattr(f, "readinto1"):
3012+
self.assertRaises(ValueError, f.readinto1, bytearray(1024))
29883013
self.assertRaises(ValueError, f.readline)
29893014
self.assertRaises(ValueError, f.readlines)
29903015
self.assertRaises(ValueError, f.seek, 0)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ Core and Builtins
9292
Library
9393
-------
9494

95+
- Issue #20578: Add io.BufferedIOBase.readinto1.
96+
9597
- Issue #21515: tempfile.TemporaryFile now uses os.O_TMPFILE flag is available.
9698

9799
- Issue #13223: Fix pydoc.writedoc so that the HTML documentation for methods

Modules/_io/bufferedio.c

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ _Py_IDENTIFIER(read);
2424
_Py_IDENTIFIER(read1);
2525
_Py_IDENTIFIER(readable);
2626
_Py_IDENTIFIER(readinto);
27+
_Py_IDENTIFIER(readinto1);
2728
_Py_IDENTIFIER(writable);
2829
_Py_IDENTIFIER(write);
2930

@@ -47,17 +48,21 @@ PyDoc_STRVAR(bufferediobase_doc,
4748
);
4849

4950
static PyObject *
50-
bufferediobase_readinto(PyObject *self, PyObject *args)
51+
_bufferediobase_readinto_generic(PyObject *self, PyObject *args, char readinto1)
5152
{
5253
Py_buffer buf;
5354
Py_ssize_t len;
5455
PyObject *data;
5556

56-
if (!PyArg_ParseTuple(args, "w*:readinto", &buf)) {
57+
if (!PyArg_ParseTuple(args,
58+
readinto1 ? "w*:readinto1" : "w*:readinto",
59+
&buf)) {
5760
return NULL;
5861
}
5962

60-
data = _PyObject_CallMethodId(self, &PyId_read, "n", buf.len);
63+
data = _PyObject_CallMethodId(self,
64+
readinto1 ? &PyId_read1 : &PyId_read,
65+
"n", buf.len);
6166
if (data == NULL)
6267
goto error;
6368

@@ -88,6 +93,18 @@ bufferediobase_readinto(PyObject *self, PyObject *args)
8893
return NULL;
8994
}
9095

96+
static PyObject *
97+
bufferediobase_readinto(PyObject *self, PyObject *args)
98+
{
99+
return _bufferediobase_readinto_generic(self, args, 0);
100+
}
101+
102+
static PyObject *
103+
bufferediobase_readinto1(PyObject *self, PyObject *args)
104+
{
105+
return _bufferediobase_readinto_generic(self, args, 1);
106+
}
107+
91108
static PyObject *
92109
bufferediobase_unsupported(const char *message)
93110
{
@@ -167,6 +184,7 @@ static PyMethodDef bufferediobase_methods[] = {
167184
{"read", bufferediobase_read, METH_VARARGS, bufferediobase_read_doc},
168185
{"read1", bufferediobase_read1, METH_VARARGS, bufferediobase_read1_doc},
169186
{"readinto", bufferediobase_readinto, METH_VARARGS, NULL},
187+
{"readinto1", bufferediobase_readinto1, METH_VARARGS, NULL},
170188
{"write", bufferediobase_write, METH_VARARGS, bufferediobase_write_doc},
171189
{NULL, NULL}
172190
};
@@ -988,15 +1006,17 @@ buffered_read1(buffered *self, PyObject *args)
9881006
}
9891007

9901008
static PyObject *
991-
buffered_readinto(buffered *self, PyObject *args)
1009+
_buffered_readinto_generic(buffered *self, PyObject *args, char readinto1)
9921010
{
9931011
Py_buffer buf;
9941012
Py_ssize_t n, written = 0, remaining;
9951013
PyObject *res = NULL;
9961014

9971015
CHECK_INITIALIZED(self)
9981016

999-
if (!PyArg_ParseTuple(args, "w*:readinto", &buf))
1017+
if (!PyArg_ParseTuple(args,
1018+
readinto1 ? "w*:readinto1" : "w*:readinto",
1019+
&buf))
10001020
return NULL;
10011021

10021022
n = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t);
@@ -1034,7 +1054,10 @@ buffered_readinto(buffered *self, PyObject *args)
10341054
n = _bufferedreader_raw_read(self, (char *) buf.buf + written,
10351055
remaining);
10361056
}
1037-
else {
1057+
1058+
/* In readinto1 mode, we do not want to fill the internal
1059+
buffer if we already have some data to return */
1060+
else if (!(readinto1 && written)) {
10381061
n = _bufferedreader_fill_buffer(self);
10391062
if (n > 0) {
10401063
if (n > remaining)
@@ -1045,6 +1068,10 @@ buffered_readinto(buffered *self, PyObject *args)
10451068
continue; /* short circuit */
10461069
}
10471070
}
1071+
else {
1072+
n = 0;
1073+
}
1074+
10481075
if (n == 0 || (n == -2 && written > 0))
10491076
break;
10501077
if (n < 0) {
@@ -1054,6 +1081,12 @@ buffered_readinto(buffered *self, PyObject *args)
10541081
}
10551082
goto end;
10561083
}
1084+
1085+
/* At most one read in readinto1 mode */
1086+
if (readinto1) {
1087+
written += n;
1088+
break;
1089+
}
10571090
}
10581091
res = PyLong_FromSsize_t(written);
10591092

@@ -1064,6 +1097,19 @@ buffered_readinto(buffered *self, PyObject *args)
10641097
return res;
10651098
}
10661099

1100+
static PyObject *
1101+
buffered_readinto(buffered *self, PyObject *args)
1102+
{
1103+
return _buffered_readinto_generic(self, args, 0);
1104+
}
1105+
1106+
static PyObject *
1107+
buffered_readinto1(buffered *self, PyObject *args)
1108+
{
1109+
return _buffered_readinto_generic(self, args, 1);
1110+
}
1111+
1112+
10671113
static PyObject *
10681114
_buffered_readline(buffered *self, Py_ssize_t limit)
10691115
{
@@ -1749,6 +1795,7 @@ static PyMethodDef bufferedreader_methods[] = {
17491795
{"peek", (PyCFunction)buffered_peek, METH_VARARGS},
17501796
{"read1", (PyCFunction)buffered_read1, METH_VARARGS},
17511797
{"readinto", (PyCFunction)buffered_readinto, METH_VARARGS},
1798+
{"readinto1", (PyCFunction)buffered_readinto1, METH_VARARGS},
17521799
{"readline", (PyCFunction)buffered_readline, METH_VARARGS},
17531800
{"seek", (PyCFunction)buffered_seek, METH_VARARGS},
17541801
{"tell", (PyCFunction)buffered_tell, METH_NOARGS},
@@ -2347,6 +2394,12 @@ bufferedrwpair_readinto(rwpair *self, PyObject *args)
23472394
return _forward_call(self->reader, &PyId_readinto, args);
23482395
}
23492396

2397+
static PyObject *
2398+
bufferedrwpair_readinto1(rwpair *self, PyObject *args)
2399+
{
2400+
return _forward_call(self->reader, &PyId_readinto1, args);
2401+
}
2402+
23502403
static PyObject *
23512404
bufferedrwpair_write(rwpair *self, PyObject *args)
23522405
{
@@ -2412,6 +2465,7 @@ static PyMethodDef bufferedrwpair_methods[] = {
24122465
{"peek", (PyCFunction)bufferedrwpair_peek, METH_VARARGS},
24132466
{"read1", (PyCFunction)bufferedrwpair_read1, METH_VARARGS},
24142467
{"readinto", (PyCFunction)bufferedrwpair_readinto, METH_VARARGS},
2468+
{"readinto1", (PyCFunction)bufferedrwpair_readinto1, METH_VARARGS},
24152469

24162470
{"write", (PyCFunction)bufferedrwpair_write, METH_VARARGS},
24172471
{"flush", (PyCFunction)bufferedrwpair_flush, METH_NOARGS},
@@ -2560,6 +2614,7 @@ static PyMethodDef bufferedrandom_methods[] = {
25602614
{"read", (PyCFunction)buffered_read, METH_VARARGS},
25612615
{"read1", (PyCFunction)buffered_read1, METH_VARARGS},
25622616
{"readinto", (PyCFunction)buffered_readinto, METH_VARARGS},
2617+
{"readinto1", (PyCFunction)buffered_readinto1, METH_VARARGS},
25632618
{"readline", (PyCFunction)buffered_readline, METH_VARARGS},
25642619
{"peek", (PyCFunction)buffered_peek, METH_VARARGS},
25652620
{"write", (PyCFunction)bufferedwriter_write, METH_VARARGS},

0 commit comments

Comments
 (0)