mirror of
https://github.com/python/cpython.git
synced 2025-08-03 08:34:29 +00:00
parent
77143dbaee
commit
a96fea03e8
4 changed files with 235 additions and 19 deletions
95
Lib/_pyio.py
95
Lib/_pyio.py
|
@ -6,6 +6,7 @@ import os
|
|||
import abc
|
||||
import codecs
|
||||
import errno
|
||||
import array
|
||||
# Import _thread instead of threading to reduce startup cost
|
||||
try:
|
||||
from _thread import allocate_lock as Lock
|
||||
|
@ -662,16 +663,33 @@ class BufferedIOBase(IOBase):
|
|||
Raises BlockingIOError if the underlying raw stream has no
|
||||
data at the moment.
|
||||
"""
|
||||
# XXX This ought to work with anything that supports the buffer API
|
||||
data = self.read(len(b))
|
||||
|
||||
return self._readinto(b, read1=False)
|
||||
|
||||
def readinto1(self, b):
|
||||
"""Read up to len(b) bytes into *b*, using at most one system call
|
||||
|
||||
Returns an int representing the number of bytes read (0 for EOF).
|
||||
|
||||
Raises BlockingIOError if the underlying raw stream has no
|
||||
data at the moment.
|
||||
"""
|
||||
|
||||
return self._readinto(b, read1=True)
|
||||
|
||||
def _readinto(self, b, read1):
|
||||
if not isinstance(b, memoryview):
|
||||
b = memoryview(b)
|
||||
b = b.cast('B')
|
||||
|
||||
if read1:
|
||||
data = self.read1(len(b))
|
||||
else:
|
||||
data = self.read(len(b))
|
||||
n = len(data)
|
||||
try:
|
||||
b[:n] = data
|
||||
except TypeError as err:
|
||||
import array
|
||||
if not isinstance(b, array.array):
|
||||
raise err
|
||||
b[:n] = array.array('b', data)
|
||||
|
||||
b[:n] = data
|
||||
|
||||
return n
|
||||
|
||||
def write(self, b):
|
||||
|
@ -1065,6 +1083,58 @@ class BufferedReader(_BufferedIOMixin):
|
|||
return self._read_unlocked(
|
||||
min(size, len(self._read_buf) - self._read_pos))
|
||||
|
||||
# Implementing readinto() and readinto1() is not strictly necessary (we
|
||||
# could rely on the base class that provides an implementation in terms of
|
||||
# read() and read1()). We do it anyway to keep the _pyio implementation
|
||||
# similar to the io implementation (which implements the methods for
|
||||
# performance reasons).
|
||||
def _readinto(self, buf, read1):
|
||||
"""Read data into *buf* with at most one system call."""
|
||||
|
||||
if len(buf) == 0:
|
||||
return 0
|
||||
|
||||
# Need to create a memoryview object of type 'b', otherwise
|
||||
# we may not be able to assign bytes to it, and slicing it
|
||||
# would create a new object.
|
||||
if not isinstance(buf, memoryview):
|
||||
buf = memoryview(buf)
|
||||
buf = buf.cast('B')
|
||||
|
||||
written = 0
|
||||
with self._read_lock:
|
||||
while written < len(buf):
|
||||
|
||||
# First try to read from internal buffer
|
||||
avail = min(len(self._read_buf) - self._read_pos, len(buf))
|
||||
if avail:
|
||||
buf[written:written+avail] = \
|
||||
self._read_buf[self._read_pos:self._read_pos+avail]
|
||||
self._read_pos += avail
|
||||
written += avail
|
||||
if written == len(buf):
|
||||
break
|
||||
|
||||
# If remaining space in callers buffer is larger than
|
||||
# internal buffer, read directly into callers buffer
|
||||
if len(buf) - written > self.buffer_size:
|
||||
n = self.raw.readinto(buf[written:])
|
||||
if not n:
|
||||
break # eof
|
||||
written += n
|
||||
|
||||
# Otherwise refill internal buffer - unless we're
|
||||
# in read1 mode and already got some data
|
||||
elif not (read1 and written):
|
||||
if not self._peek_unlocked(1):
|
||||
break # eof
|
||||
|
||||
# In readinto1 mode, return as soon as we have some data
|
||||
if read1 and written:
|
||||
break
|
||||
|
||||
return written
|
||||
|
||||
def tell(self):
|
||||
return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos
|
||||
|
||||
|
@ -1214,6 +1284,9 @@ class BufferedRWPair(BufferedIOBase):
|
|||
def read1(self, size):
|
||||
return self.reader.read1(size)
|
||||
|
||||
def readinto1(self, b):
|
||||
return self.reader.readinto1(b)
|
||||
|
||||
def readable(self):
|
||||
return self.reader.readable()
|
||||
|
||||
|
@ -1296,6 +1369,10 @@ class BufferedRandom(BufferedWriter, BufferedReader):
|
|||
self.flush()
|
||||
return BufferedReader.read1(self, size)
|
||||
|
||||
def readinto1(self, b):
|
||||
self.flush()
|
||||
return BufferedReader.readinto1(self, b)
|
||||
|
||||
def write(self, b):
|
||||
if self._read_buf:
|
||||
# Undo readahead
|
||||
|
|
|
@ -943,6 +943,71 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
|
|||
self.assertEqual(bufio.readinto(b), 1)
|
||||
self.assertEqual(b, b"cb")
|
||||
|
||||
def test_readinto1(self):
|
||||
buffer_size = 10
|
||||
rawio = self.MockRawIO((b"abc", b"de", b"fgh", b"jkl"))
|
||||
bufio = self.tp(rawio, buffer_size=buffer_size)
|
||||
b = bytearray(2)
|
||||
self.assertEqual(bufio.peek(3), b'abc')
|
||||
self.assertEqual(rawio._reads, 1)
|
||||
self.assertEqual(bufio.readinto1(b), 2)
|
||||
self.assertEqual(b, b"ab")
|
||||
self.assertEqual(rawio._reads, 1)
|
||||
self.assertEqual(bufio.readinto1(b), 1)
|
||||
self.assertEqual(b[:1], b"c")
|
||||
self.assertEqual(rawio._reads, 1)
|
||||
self.assertEqual(bufio.readinto1(b), 2)
|
||||
self.assertEqual(b, b"de")
|
||||
self.assertEqual(rawio._reads, 2)
|
||||
b = bytearray(2*buffer_size)
|
||||
self.assertEqual(bufio.peek(3), b'fgh')
|
||||
self.assertEqual(rawio._reads, 3)
|
||||
self.assertEqual(bufio.readinto1(b), 6)
|
||||
self.assertEqual(b[:6], b"fghjkl")
|
||||
self.assertEqual(rawio._reads, 4)
|
||||
|
||||
def test_readinto_array(self):
|
||||
buffer_size = 60
|
||||
data = b"a" * 26
|
||||
rawio = self.MockRawIO((data,))
|
||||
bufio = self.tp(rawio, buffer_size=buffer_size)
|
||||
|
||||
# Create an array with element size > 1 byte
|
||||
b = array.array('i', b'x' * 32)
|
||||
assert len(b) != 16
|
||||
|
||||
# Read into it. We should get as many *bytes* as we can fit into b
|
||||
# (which is more than the number of elements)
|
||||
n = bufio.readinto(b)
|
||||
self.assertGreater(n, len(b))
|
||||
|
||||
# Check that old contents of b are preserved
|
||||
bm = memoryview(b).cast('B')
|
||||
self.assertLess(n, len(bm))
|
||||
self.assertEqual(bm[:n], data[:n])
|
||||
self.assertEqual(bm[n:], b'x' * (len(bm[n:])))
|
||||
|
||||
def test_readinto1_array(self):
|
||||
buffer_size = 60
|
||||
data = b"a" * 26
|
||||
rawio = self.MockRawIO((data,))
|
||||
bufio = self.tp(rawio, buffer_size=buffer_size)
|
||||
|
||||
# Create an array with element size > 1 byte
|
||||
b = array.array('i', b'x' * 32)
|
||||
assert len(b) != 16
|
||||
|
||||
# Read into it. We should get as many *bytes* as we can fit into b
|
||||
# (which is more than the number of elements)
|
||||
n = bufio.readinto1(b)
|
||||
self.assertGreater(n, len(b))
|
||||
|
||||
# Check that old contents of b are preserved
|
||||
bm = memoryview(b).cast('B')
|
||||
self.assertLess(n, len(bm))
|
||||
self.assertEqual(bm[:n], data[:n])
|
||||
self.assertEqual(bm[n:], b'x' * (len(bm[n:])))
|
||||
|
||||
def test_readlines(self):
|
||||
def bufio():
|
||||
rawio = self.MockRawIO((b"abc\n", b"d\n", b"ef"))
|
||||
|
@ -3050,6 +3115,8 @@ class MiscIOTest(unittest.TestCase):
|
|||
self.assertRaises(ValueError, f.readall)
|
||||
if hasattr(f, "readinto"):
|
||||
self.assertRaises(ValueError, f.readinto, bytearray(1024))
|
||||
if hasattr(f, "readinto1"):
|
||||
self.assertRaises(ValueError, f.readinto1, bytearray(1024))
|
||||
self.assertRaises(ValueError, f.readline)
|
||||
self.assertRaises(ValueError, f.readlines)
|
||||
self.assertRaises(ValueError, f.seek, 0)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue