From ddd392cbb9a94355a2ea32da4a42371d1333bfb8 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sun, 13 Dec 2009 19:19:07 +0000 Subject: [PATCH] accept None as the same as having passed no argument in file types #7349 This is for consistency with imitation file objects like StringIO and BytesIO. This commit also adds a few tests, where they were lacking for concerned methods. --- Lib/test/test_fileio.py | 9 +++++++++ Lib/test/test_io.py | 36 ++++++++++++++++++++++++++++++++---- Misc/NEWS | 4 ++++ Modules/_io/_iomodule.c | 23 +++++++++++++++++++++++ Modules/_io/_iomodule.h | 3 +++ Modules/_io/bufferedio.c | 18 ++---------------- Modules/_io/fileio.c | 2 +- Modules/_io/iobase.c | 9 ++------- Modules/_io/textio.c | 2 +- 9 files changed, 77 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 87bf01549c8..91c5251d42b 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -71,6 +71,15 @@ class AutoFileTests(unittest.TestCase): n = self.f.readinto(a) self.assertEquals(array(b'b', [1, 2]), a[:n]) + def test_none_args(self): + self.f.write(b"hi\nbye\nabc") + self.f.close() + self.f = _FileIO(TESTFN, 'r') + self.assertEqual(self.f.read(None), b"hi\nbye\nabc") + self.f.seek(0) + self.assertEqual(self.f.readline(None), b"hi\n") + self.assertEqual(self.f.readlines(None), [b"bye\n", b"abc"]) + def testRepr(self): self.assertEquals(repr(self.f), "<_io.FileIO name=%r mode='%s'>" % (self.f.name, self.f.mode)) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 4288f1ba05c..a90dd504a38 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -341,7 +341,7 @@ class IOTest(unittest.TestCase): self.assertEqual(f.readline(2), b"xy") self.assertEqual(f.readline(4), b"zzy\n") self.assertEqual(f.readline(), b"foo\x00bar\n") - self.assertEqual(f.readline(), b"another line") + self.assertEqual(f.readline(None), b"another line") self.assertRaises(TypeError, f.readline, 5.3) with self.open(support.TESTFN, "r") as f: self.assertRaises(TypeError, f.readline, 5.3) @@ -654,9 +654,10 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEquals(b"abc", bufio.read()) def test_read(self): - rawio = self.MockRawIO((b"abc", b"d", b"efg")) - bufio = self.tp(rawio) - self.assertEquals(b"abcdef", bufio.read(6)) + for arg in (None, 7): + rawio = self.MockRawIO((b"abc", b"d", b"efg")) + bufio = self.tp(rawio) + self.assertEquals(b"abcdefg", bufio.read(arg)) # Invalid args self.assertRaises(ValueError, bufio.read, -2) @@ -673,6 +674,7 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEquals(b"efg", bufio.read1(100)) self.assertEquals(rawio._reads, 3) self.assertEquals(b"", bufio.read1(100)) + self.assertEquals(rawio._reads, 4) # Invalid args self.assertRaises(ValueError, bufio.read1, -1) @@ -691,6 +693,14 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertEquals(bufio.readinto(b), 0) self.assertEquals(b, b"gf") + def test_readlines(self): + def bufio(): + rawio = self.MockRawIO((b"abc\n", b"d\n", b"ef")) + return self.tp(rawio) + self.assertEquals(bufio().readlines(), [b"abc\n", b"d\n", b"ef"]) + self.assertEquals(bufio().readlines(5), [b"abc\n", b"d\n"]) + self.assertEquals(bufio().readlines(None), [b"abc\n", b"d\n", b"ef"]) + def test_buffering(self): data = b"abcdefghi" dlen = len(data) @@ -1131,6 +1141,14 @@ class BufferedRWPairTest(unittest.TestCase): self.assertEqual(pair.read(3), b"abc") self.assertEqual(pair.read(1), b"d") self.assertEqual(pair.read(), b"ef") + pair = self.tp(self.BytesIO(b"abc"), self.MockRawIO()) + self.assertEqual(pair.read(None), b"abc") + + def test_readlines(self): + pair = lambda: self.tp(self.BytesIO(b"abc\ndef\nh"), self.MockRawIO()) + self.assertEqual(pair().readlines(), [b"abc\n", b"def\n", b"h"]) + self.assertEqual(pair().readlines(), [b"abc\n", b"def\n", b"h"]) + self.assertEqual(pair().readlines(5), [b"abc\n", b"def\n"]) def test_read1(self): # .read1() is delegated to the underlying reader object, so this test @@ -1781,6 +1799,8 @@ class TextIOWrapperTest(unittest.TestCase): self.assertEquals(f.read(), "abc") cookie = f.tell() self.assertEquals(f.seek(0), 0) + self.assertEquals(f.read(None), "abc") + f.seek(0) self.assertEquals(f.read(2), "ab") self.assertEquals(f.read(1), "c") self.assertEquals(f.read(1), "") @@ -1951,6 +1971,14 @@ class TextIOWrapperTest(unittest.TestCase): reads += c self.assertEquals(reads, "AA\nBB") + def test_readlines(self): + txt = self.TextIOWrapper(self.BytesIO(b"AA\nBB\nCC")) + self.assertEqual(txt.readlines(), ["AA\n", "BB\n", "CC"]) + txt.seek(0) + self.assertEqual(txt.readlines(None), ["AA\n", "BB\n", "CC"]) + txt.seek(0) + self.assertEqual(txt.readlines(5), ["AA\n", "BB\n"]) + # read in amounts equal to TextIOWrapper._CHUNK_SIZE which is 128. def test_read_by_chunk(self): # make sure "\r\n" straddles 128 char boundary. diff --git a/Misc/NEWS b/Misc/NEWS index 9c08b432a16..cfdf975e20f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -20,6 +20,10 @@ Core and Builtins Library ------- +- Issue #7349: Make methods of file objects in the io module accept None as an + argument where file-like objects (ie StringIO and BytesIO) accept them to mean + the same as passing no argument. + - Issue #7348: StringIO.StringIO.readline(-1) now acts as if it got no argument like other file objects. diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index 7b430228091..a2a64304b13 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -573,6 +573,29 @@ PyNumber_AsOff_t(PyObject *item, PyObject *err) } +/* Basically the "n" format code with the ability to turn None into -1. */ +int +_PyIO_ConvertSsize_t(PyObject *obj, void *result) { + Py_ssize_t limit; + if (obj == Py_None) { + limit = -1; + } + else if (PyNumber_Check(obj)) { + limit = PyNumber_AsSsize_t(obj, PyExc_OverflowError); + if (limit == -1 && PyErr_Occurred()) + return 0; + } + else { + PyErr_Format(PyExc_TypeError, + "integer argument expected, got '%.200s'", + Py_TYPE(obj)->tp_name); + return 0; + } + *((Py_ssize_t *)result) = limit; + return 1; +} + + /* * Module definition */ diff --git a/Modules/_io/_iomodule.h b/Modules/_io/_iomodule.h index 790d9193c5d..0fa53910509 100644 --- a/Modules/_io/_iomodule.h +++ b/Modules/_io/_iomodule.h @@ -19,6 +19,9 @@ extern PyTypeObject PyBufferedRandom_Type; extern PyTypeObject PyTextIOWrapper_Type; extern PyTypeObject PyIncrementalNewlineDecoder_Type; + +extern int _PyIO_ConvertSsize_t(PyObject *, void *); + /* These functions are used as METH_NOARGS methods, are normally called * with args=NULL, and return a new reference. * BUT when args=Py_True is passed, they return a borrowed reference. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 3ab7906a930..ba937eda837 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -720,7 +720,7 @@ buffered_read(buffered *self, PyObject *args) PyObject *res; CHECK_INITIALIZED(self) - if (!PyArg_ParseTuple(args, "|n:read", &n)) { + if (!PyArg_ParseTuple(args, "|O&:read", &_PyIO_ConvertSsize_t, &n)) { return NULL; } if (n < -1) { @@ -950,25 +950,11 @@ end_unlocked: static PyObject * buffered_readline(buffered *self, PyObject *args) { - PyObject *limitobj = NULL; Py_ssize_t limit = -1; CHECK_INITIALIZED(self) - - if (!PyArg_ParseTuple(args, "|O:readline", &limitobj)) { + if (!PyArg_ParseTuple(args, "|O&:readline", &_PyIO_ConvertSsize_t, &limit)) return NULL; - } - if (limitobj) { - if (!PyNumber_Check(limitobj)) { - PyErr_Format(PyExc_TypeError, - "integer argument expected, got '%.200s'", - Py_TYPE(limitobj)->tp_name); - return NULL; - } - limit = PyNumber_AsSsize_t(limitobj, PyExc_OverflowError); - if (limit == -1 && PyErr_Occurred()) - return NULL; - } return _buffered_readline(self, limit); } diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index e98de9e69fa..164fe5bc081 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -599,7 +599,7 @@ fileio_read(fileio *self, PyObject *args) if (!self->readable) return err_mode("reading"); - if (!PyArg_ParseTuple(args, "|n", &size)) + if (!PyArg_ParseTuple(args, "|O&", &_PyIO_ConvertSsize_t, &size)) return NULL; if (size < 0) { diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index 38080f7f8a9..bd6cd5f8deb 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -455,7 +455,7 @@ iobase_readline(PyObject *self, PyObject *args) PyObject *buffer, *result; Py_ssize_t old_size = -1; - if (!PyArg_ParseTuple(args, "|n:readline", &limit)) { + if (!PyArg_ParseTuple(args, "|O&:readline", &_PyIO_ConvertSsize_t, &limit)) { return NULL; } @@ -579,14 +579,9 @@ iobase_readlines(PyObject *self, PyObject *args) Py_ssize_t hint = -1, length = 0; PyObject *hintobj = Py_None, *result; - if (!PyArg_ParseTuple(args, "|O:readlines", &hintobj)) { + if (!PyArg_ParseTuple(args, "|O&:readlines", &_PyIO_ConvertSsize_t, &hint)) { return NULL; } - if (hintobj != Py_None) { - hint = PyNumber_AsSsize_t(hintobj, PyExc_ValueError); - if (hint == -1 && PyErr_Occurred()) - return NULL; - } result = PyList_New(0); if (result == NULL) diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 2f99a606b2e..91fecae761f 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1455,7 +1455,7 @@ textiowrapper_read(textio *self, PyObject *args) CHECK_INITIALIZED(self); - if (!PyArg_ParseTuple(args, "|n:read", &n)) + if (!PyArg_ParseTuple(args, "|O&:read", &_PyIO_ConvertSsize_t, &n)) return NULL; CHECK_CLOSED(self);