Issue #20699: Document that “io” methods should accept memoryview
This matches the usage by BufferedReader, BufferedWriter, etc. Also document
and test that the write() methods should only access their argument before
they return.
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index ccd6ce8..11348b2 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -277,8 +277,9 @@
may raise a IOError when operations they do not support are called.
The basic type used for binary data read from or written to a file is
- bytes. bytearrays are accepted too, and in some cases (such as
- readinto) needed. Text I/O classes work with str data.
+ the bytes type. Method arguments may also be bytearray or memoryview of
+ arrays of bytes. In some cases, such as readinto, a writable object such
+ as bytearray is required. Text I/O classes work with unicode data.
Note that calling any method (even inquiries) on a closed stream is
undefined. Implementations may raise IOError in this case.
@@ -649,7 +650,6 @@
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))
n = len(data)
try:
@@ -664,8 +664,7 @@
def write(self, b):
"""Write the given buffer to the IO stream.
- Return the number of bytes written, which is never less than
- len(b).
+ Return the number of bytes written, which is always len(b).
Raises BlockingIOError if the buffer is full and the
underlying raw stream cannot accept more data at the moment.
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index b7f6046..e26ffba 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -54,6 +54,9 @@
__metaclass__ = type
bytes = support.py3k_bytes
+def byteslike(*pos, **kw):
+ return memoryview(bytearray(*pos, **kw))
+
def _default_chunk_size():
"""Get the default TextIOWrapper chunk size"""
with io.open(__file__, "r", encoding="latin1") as f:
@@ -273,7 +276,9 @@
self.assertEqual(f.tell(), 6)
self.assertEqual(f.seek(-1, 1), 5)
self.assertEqual(f.tell(), 5)
- self.assertEqual(f.write(bytearray(b" world\n\n\n")), 9)
+ buffer = bytearray(b" world\n\n\n")
+ self.assertEqual(f.write(buffer), 9)
+ buffer[:] = b"*" * 9 # Overwrite our copy of the data
self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(b"h"), 1)
self.assertEqual(f.seek(-1, 2), 13)
@@ -286,20 +291,21 @@
def read_ops(self, f, buffered=False):
data = f.read(5)
self.assertEqual(data, b"hello")
- data = bytearray(data)
+ data = byteslike(data)
self.assertEqual(f.readinto(data), 5)
- self.assertEqual(data, b" worl")
+ self.assertEqual(data.tobytes(), b" worl")
+ data = bytearray(5)
self.assertEqual(f.readinto(data), 2)
self.assertEqual(len(data), 5)
self.assertEqual(data[:2], b"d\n")
self.assertEqual(f.seek(0), 0)
self.assertEqual(f.read(20), b"hello world\n")
self.assertEqual(f.read(1), b"")
- self.assertEqual(f.readinto(bytearray(b"x")), 0)
+ self.assertEqual(f.readinto(byteslike(b"x")), 0)
self.assertEqual(f.seek(-6, 2), 6)
self.assertEqual(f.read(5), b"world")
self.assertEqual(f.read(0), b"")
- self.assertEqual(f.readinto(bytearray()), 0)
+ self.assertEqual(f.readinto(byteslike()), 0)
self.assertEqual(f.seek(-6, 1), 5)
self.assertEqual(f.read(5), b" worl")
self.assertEqual(f.tell(), 10)
@@ -649,6 +655,16 @@
support.gc_collect()
self.assertEqual(recorded, [])
+ def test_buffered_readinto_mixin(self):
+ # Test the implementation provided by BufferedIOBase
+ class Stream(self.BufferedIOBase):
+ def read(self, size):
+ return b"12345"
+ stream = Stream()
+ buffer = byteslike(5)
+ self.assertEqual(stream.readinto(buffer), 5)
+ self.assertEqual(buffer.tobytes(), b"12345")
+
class CIOTest(IOTest):
@@ -1111,6 +1127,11 @@
bufio = self.tp(writer, 8)
bufio.write(b"abc")
self.assertFalse(writer._write_stack)
+ buffer = bytearray(b"def")
+ bufio.write(buffer)
+ buffer[:] = b"***" # Overwrite our copy of the data
+ bufio.flush()
+ self.assertEqual(b"".join(writer._write_stack), b"abcdef")
def test_write_overflow(self):
writer = self.MockRawIO()
@@ -1440,9 +1461,9 @@
def test_readinto(self):
pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO())
- data = bytearray(5)
+ data = byteslike(5)
self.assertEqual(pair.readinto(data), 5)
- self.assertEqual(data, b"abcde")
+ self.assertEqual(data.tobytes(), b"abcde")
def test_write(self):
w = self.MockRawIO()
@@ -1450,7 +1471,9 @@
pair.write(b"abc")
pair.flush()
- pair.write(b"def")
+ buffer = bytearray(b"def")
+ pair.write(buffer)
+ buffer[:] = b"***" # Overwrite our copy of the data
pair.flush()
self.assertEqual(w._write_stack, [b"abc", b"def"])
diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py
index 0eb9961..2c18ad7 100644
--- a/Lib/test/test_memoryio.py
+++ b/Lib/test/test_memoryio.py
@@ -396,6 +396,7 @@
class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
+ # Test _pyio.BytesIO; class also inherited for testing C implementation
UnsupportedOperation = pyio.UnsupportedOperation