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

Skip to content

Commit a96fea0

Browse files
committed
add BufferedIOBase.readinto1 (closes #20578)
Patch by Nikolaus Rath.
1 parent 77143db commit a96fea0

4 files changed

Lines changed: 235 additions & 19 deletions

File tree

Doc/library/io.rst

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,11 @@ I/O Base Classes
465465

466466
.. method:: read1(size=-1)
467467

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

473474
.. method:: readinto(b)
474475

@@ -481,6 +482,18 @@ I/O Base Classes
481482
A :exc:`BlockingIOError` is raised if the underlying raw stream is in
482483
non blocking-mode, and has no data available at the moment.
483484

485+
.. method:: readinto1(b)
486+
487+
Read up to ``len(b)`` bytes into bytearray *b*, ,using at most
488+
one call to the underlying raw stream's :meth:`~RawIOBase.read`
489+
(or :meth:`~RawIOBase.readinto`) method. Return the number of
490+
bytes read.
491+
492+
A :exc:`BlockingIOError` is raised if the underlying raw stream is in
493+
non blocking-mode, and has no data available at the moment.
494+
495+
.. versionadded:: 3.5
496+
484497
.. method:: write(b)
485498

486499
Write the given :class:`bytes` or :class:`bytearray` object, *b* and
@@ -596,6 +609,11 @@ than raw I/O does.
596609

597610
In :class:`BytesIO`, this is the same as :meth:`read`.
598611

612+
.. method:: readinto1()
613+
614+
In :class:`BytesIO`, this is the same as :meth:`readinto`.
615+
616+
.. versionadded:: 3.5
599617

600618
.. class:: BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE)
601619

Lib/_pyio.py

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import abc
77
import codecs
88
import errno
9+
import array
910
# Import _thread instead of threading to reduce startup cost
1011
try:
1112
from _thread import allocate_lock as Lock
@@ -662,16 +663,33 @@ def readinto(self, b):
662663
Raises BlockingIOError if the underlying raw stream has no
663664
data at the moment.
664665
"""
665-
# XXX This ought to work with anything that supports the buffer API
666-
data = self.read(len(b))
666+
667+
return self._readinto(b, read1=False)
668+
669+
def readinto1(self, b):
670+
"""Read up to len(b) bytes into *b*, using at most one system call
671+
672+
Returns an int representing the number of bytes read (0 for EOF).
673+
674+
Raises BlockingIOError if the underlying raw stream has no
675+
data at the moment.
676+
"""
677+
678+
return self._readinto(b, read1=True)
679+
680+
def _readinto(self, b, read1):
681+
if not isinstance(b, memoryview):
682+
b = memoryview(b)
683+
b = b.cast('B')
684+
685+
if read1:
686+
data = self.read1(len(b))
687+
else:
688+
data = self.read(len(b))
667689
n = len(data)
668-
try:
669-
b[:n] = data
670-
except TypeError as err:
671-
import array
672-
if not isinstance(b, array.array):
673-
raise err
674-
b[:n] = array.array('b', data)
690+
691+
b[:n] = data
692+
675693
return n
676694

677695
def write(self, b):
@@ -1065,6 +1083,58 @@ def read1(self, size):
10651083
return self._read_unlocked(
10661084
min(size, len(self._read_buf) - self._read_pos))
10671085

1086+
# Implementing readinto() and readinto1() is not strictly necessary (we
1087+
# could rely on the base class that provides an implementation in terms of
1088+
# read() and read1()). We do it anyway to keep the _pyio implementation
1089+
# similar to the io implementation (which implements the methods for
1090+
# performance reasons).
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+
# Need to create a memoryview object of type 'b', otherwise
1098+
# we may not be able to assign bytes to it, and slicing it
1099+
# would create a new object.
1100+
if not isinstance(buf, memoryview):
1101+
buf = memoryview(buf)
1102+
buf = buf.cast('B')
1103+
1104+
written = 0
1105+
with self._read_lock:
1106+
while written < len(buf):
1107+
1108+
# First try to read from internal buffer
1109+
avail = min(len(self._read_buf) - self._read_pos, len(buf))
1110+
if avail:
1111+
buf[written:written+avail] = \
1112+
self._read_buf[self._read_pos:self._read_pos+avail]
1113+
self._read_pos += avail
1114+
written += avail
1115+
if written == len(buf):
1116+
break
1117+
1118+
# If remaining space in callers buffer is larger than
1119+
# internal buffer, read directly into callers buffer
1120+
if len(buf) - written > self.buffer_size:
1121+
n = self.raw.readinto(buf[written:])
1122+
if not n:
1123+
break # eof
1124+
written += n
1125+
1126+
# Otherwise refill internal buffer - unless we're
1127+
# in read1 mode and already got some data
1128+
elif not (read1 and written):
1129+
if not self._peek_unlocked(1):
1130+
break # eof
1131+
1132+
# In readinto1 mode, return as soon as we have some data
1133+
if read1 and written:
1134+
break
1135+
1136+
return written
1137+
10681138
def tell(self):
10691139
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
10701140

@@ -1214,6 +1284,9 @@ def peek(self, size=0):
12141284
def read1(self, size):
12151285
return self.reader.read1(size)
12161286

1287+
def readinto1(self, b):
1288+
return self.reader.readinto1(b)
1289+
12171290
def readable(self):
12181291
return self.reader.readable()
12191292

@@ -1296,6 +1369,10 @@ def read1(self, size):
12961369
self.flush()
12971370
return BufferedReader.read1(self, size)
12981371

1372+
def readinto1(self, b):
1373+
self.flush()
1374+
return BufferedReader.readinto1(self, b)
1375+
12991376
def write(self, b):
13001377
if self._read_buf:
13011378
# Undo readahead

Lib/test/test_io.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,71 @@ def test_readinto(self):
943943
self.assertEqual(bufio.readinto(b), 1)
944944
self.assertEqual(b, b"cb")
945945

946+
def test_readinto1(self):
947+
buffer_size = 10
948+
rawio = self.MockRawIO((b"abc", b"de", b"fgh", b"jkl"))
949+
bufio = self.tp(rawio, buffer_size=buffer_size)
950+
b = bytearray(2)
951+
self.assertEqual(bufio.peek(3), b'abc')
952+
self.assertEqual(rawio._reads, 1)
953+
self.assertEqual(bufio.readinto1(b), 2)
954+
self.assertEqual(b, b"ab")
955+
self.assertEqual(rawio._reads, 1)
956+
self.assertEqual(bufio.readinto1(b), 1)
957+
self.assertEqual(b[:1], b"c")
958+
self.assertEqual(rawio._reads, 1)
959+
self.assertEqual(bufio.readinto1(b), 2)
960+
self.assertEqual(b, b"de")
961+
self.assertEqual(rawio._reads, 2)
962+
b = bytearray(2*buffer_size)
963+
self.assertEqual(bufio.peek(3), b'fgh')
964+
self.assertEqual(rawio._reads, 3)
965+
self.assertEqual(bufio.readinto1(b), 6)
966+
self.assertEqual(b[:6], b"fghjkl")
967+
self.assertEqual(rawio._reads, 4)
968+
969+
def test_readinto_array(self):
970+
buffer_size = 60
971+
data = b"a" * 26
972+
rawio = self.MockRawIO((data,))
973+
bufio = self.tp(rawio, buffer_size=buffer_size)
974+
975+
# Create an array with element size > 1 byte
976+
b = array.array('i', b'x' * 32)
977+
assert len(b) != 16
978+
979+
# Read into it. We should get as many *bytes* as we can fit into b
980+
# (which is more than the number of elements)
981+
n = bufio.readinto(b)
982+
self.assertGreater(n, len(b))
983+
984+
# Check that old contents of b are preserved
985+
bm = memoryview(b).cast('B')
986+
self.assertLess(n, len(bm))
987+
self.assertEqual(bm[:n], data[:n])
988+
self.assertEqual(bm[n:], b'x' * (len(bm[n:])))
989+
990+
def test_readinto1_array(self):
991+
buffer_size = 60
992+
data = b"a" * 26
993+
rawio = self.MockRawIO((data,))
994+
bufio = self.tp(rawio, buffer_size=buffer_size)
995+
996+
# Create an array with element size > 1 byte
997+
b = array.array('i', b'x' * 32)
998+
assert len(b) != 16
999+
1000+
# Read into it. We should get as many *bytes* as we can fit into b
1001+
# (which is more than the number of elements)
1002+
n = bufio.readinto1(b)
1003+
self.assertGreater(n, len(b))
1004+
1005+
# Check that old contents of b are preserved
1006+
bm = memoryview(b).cast('B')
1007+
self.assertLess(n, len(bm))
1008+
self.assertEqual(bm[:n], data[:n])
1009+
self.assertEqual(bm[n:], b'x' * (len(bm[n:])))
1010+
9461011
def test_readlines(self):
9471012
def bufio():
9481013
rawio = self.MockRawIO((b"abc\n", b"d\n", b"ef"))
@@ -3050,6 +3115,8 @@ def test_io_after_close(self):
30503115
self.assertRaises(ValueError, f.readall)
30513116
if hasattr(f, "readinto"):
30523117
self.assertRaises(ValueError, f.readinto, bytearray(1024))
3118+
if hasattr(f, "readinto1"):
3119+
self.assertRaises(ValueError, f.readinto1, bytearray(1024))
30533120
self.assertRaises(ValueError, f.readline)
30543121
self.assertRaises(ValueError, f.readlines)
30553122
self.assertRaises(ValueError, f.seek, 0)

0 commit comments

Comments
 (0)