Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 59847) +++ Python/pythonrun.c (working copy) @@ -254,11 +254,11 @@ initsigs(); /* Signal handling stuff, including initintr() */ initmain(); /* Module __main__ */ + if (!Py_NoSiteFlag) + initsite(); /* Module site */ if (initstdio() < 0) Py_FatalError( "Py_Initialize: can't initialize sys standard streams"); - if (!Py_NoSiteFlag) - initsite(); /* Module site */ /* auto-thread-state API, if available */ #ifdef WITH_THREAD Index: setup.py =================================================================== --- setup.py (revision 59847) +++ setup.py (working copy) @@ -409,6 +409,8 @@ exts.append( Extension('operator', ['operator.c']) ) # _functools exts.append( Extension("_functools", ["_functoolsmodule.c"]) ) + # BytesIO accelerator module + exts.append( Extension("_bytesio", ["_bytesio.c"]) ) # atexit exts.append( Extension("atexit", ["atexitmodule.c"]) ) # Python C API test module Index: Lib/io.py =================================================================== --- Lib/io.py (revision 59847) +++ Lib/io.py (working copy) @@ -394,6 +394,7 @@ def readline(self, limit: int = -1) -> bytes: """For backwards compatibility, a (slowish) readline().""" + self._checkClosed() if hasattr(self, "peek"): def nreadahead(): readahead = self.peek(1, unsafe=True) @@ -429,7 +430,7 @@ return line def readlines(self, hint=None): - if hint is None: + if hint is None or hint <= 0: return list(self) n = 0 lines = [] @@ -626,6 +627,8 @@ if pos is None: pos = self.tell() + # XXX: Should seek() be used, instead of passing the position + # XXX directly to truncate? return self.raw.truncate(pos) ### Flush and close ### @@ -665,7 +668,7 @@ return self.raw.isatty() -class BytesIO(BufferedIOBase): +class _BytesIO(BufferedIOBase): """Buffered I/O implementation using an in-memory bytes buffer.""" @@ -679,13 +682,19 @@ self._pos = 0 def getvalue(self): + if self.closed: + raise ValueError("getvalue of closed file") return bytes(self._buffer) def read(self, n=None): + if self.closed: + raise ValueError("read from closed file") if n is None: n = -1 if n < 0: n = len(self._buffer) + if len(self._buffer) <= self._pos: + return self._buffer[:0] newpos = min(len(self._buffer), self._pos + n) b = self._buffer[self._pos : newpos] self._pos = newpos @@ -700,6 +709,8 @@ if isinstance(b, str): raise TypeError("can't write str to binary stream") n = len(b) + if n == 0: + return 0 newpos = self._pos + n if newpos > len(self._buffer): # Inserts null bytes between the current end of the file @@ -711,28 +722,38 @@ return n def seek(self, pos, whence=0): + if self.closed: + raise ValueError("seek on closed file") try: pos = pos.__index__() except AttributeError as err: raise TypeError("an integer is required") from err if whence == 0: self._pos = max(0, pos) + if pos < 0: + raise ValueError("Negative seek position %r" % (pos,)) elif whence == 1: self._pos = max(0, self._pos + pos) elif whence == 2: self._pos = max(0, len(self._buffer) + pos) else: - raise IOError("invalid whence value") + raise ValueError("invalid whence value") return self._pos def tell(self): + if self.closed: + raise ValueError("tell on closed file") return self._pos def truncate(self, pos=None): + if self.closed: + raise ValueError("truncate on closed file") if pos is None: pos = self._pos + elif pos < 0: + raise ValueError("Negative truncate position %r" % (pos,)) del self._buffer[pos:] - return pos + return self.seek(pos) def readable(self): return True @@ -743,7 +764,17 @@ def seekable(self): return True +# Use the faster implementation of BytesIO if available +try: + from _bytesio import _BytesIO + + class BytesIO(_BytesIO, BufferedIOBase): + __doc__ = _BytesIO.__doc__ +except ImportError: + BytesIO = _BytesIO + + class BufferedReader(_BufferedIOMixin): """Buffer for a readable sequential RawIO object.""" @@ -871,6 +902,12 @@ raise BlockingIOError(e.errno, e.strerror, overage) return written + def truncate(self, pos=None): + self.flush() + if pos is None: + pos = self.raw.tell() + return self.raw.truncate(pos) + def flush(self): if self.closed: raise ValueError("flush of closed file") @@ -980,6 +1017,13 @@ else: return self.raw.tell() - len(self._read_buf) + def truncate(self, pos=None): + if pos is None: + pos = self.tell() + # Use seek to flush the read buffer. + self.seek(pos) + return BufferedWriter.truncate(self) + def read(self, n=None): if n is None: n = -1 @@ -1026,14 +1070,6 @@ """write(s: str) -> int. Write string s to stream.""" self._unsupported("write") - def truncate(self, pos: int = None) -> int: - """truncate(pos: int = None) -> int. Truncate size to pos.""" - self.flush() - if pos is None: - pos = self.tell() - self.seek(pos) - return self.buffer.truncate() - def readline(self) -> str: """readline() -> str. Read until newline or EOF. @@ -1207,9 +1243,15 @@ # were rendered by the decoder after feeding it those bytes. We # use this to reconstruct intermediate decoder states in tell(). - def _seekable(self): + def seekable(self): return self._seekable + def readable(self): + return self.buffer.readable() + + def writable(self): + return self.buffer.writable() + def flush(self): self.buffer.flush() self._telling = self._seekable @@ -1327,7 +1369,17 @@ finally: decoder.setstate(saved_state) + def truncate(self, pos: int=None) -> int: + """truncate(pos: int=None) -> int. Truncate size to pos.""" + self.flush() + if pos is None: + pos = self.tell() + self.seek(pos) + return self.buffer.truncate() + def seek(self, pos, whence=0): + if self.closed: + raise ValueError("tell on closed file") if not self._seekable: raise IOError("Underlying stream is not seekable") if whence == 1: @@ -1397,6 +1449,8 @@ return line def readline(self, limit=None): + if self.closed: + raise ValueError("read from closed file") if limit is None: limit = -1 if limit >= 0: Index: Lib/test/test_io.py =================================================================== --- Lib/test/test_io.py (revision 59847) +++ Lib/test/test_io.py (working copy) @@ -94,7 +94,7 @@ self.assertEqual(f.seek(-1, 2), 13) self.assertEqual(f.tell(), 13) self.assertEqual(f.truncate(12), 12) - self.assertEqual(f.tell(), 13) + self.assertEqual(f.tell(), 12) self.assertRaises(TypeError, f.seek, 0.0) def read_ops(self, f, buffered=False): @@ -139,7 +139,7 @@ self.assertEqual(f.tell(), self.LARGE + 2) self.assertEqual(f.seek(0, 2), self.LARGE + 2) self.assertEqual(f.truncate(self.LARGE + 1), self.LARGE + 1) - self.assertEqual(f.tell(), self.LARGE + 2) + self.assertEqual(f.tell(), self.LARGE + 1) self.assertEqual(f.seek(0, 2), self.LARGE + 1) self.assertEqual(f.seek(-1, 2), self.LARGE) self.assertEqual(f.read(2), b"x") @@ -580,6 +580,7 @@ txt.write("BB\nCCC\n") txt.write("X\rY\r\nZ") txt.flush() + self.assertEquals(buf.closed, False) self.assertEquals(buf.getvalue(), expected) def testNewlines(self): @@ -660,7 +661,8 @@ txt = io.TextIOWrapper(buf, encoding="ascii", newline=newline) txt.write(data) txt.close() - self.assertEquals(buf.getvalue(), expected) + self.assertEquals(buf.closed, True) + self.assertRaises(ValueError, buf.getvalue) finally: os.linesep = save_linesep Index: Lib/test/test_StringIO.py =================================================================== --- Lib/test/test_StringIO.py (revision 59847) +++ Lib/test/test_StringIO.py (working copy) @@ -1,127 +0,0 @@ -# Tests StringIO and cStringIO - -import sys -import unittest -import io -from test import test_support - - -class TestGenericStringIO: - # use a class variable CLASS to define which class is being tested - CLASS = None - - # Line of data to test as string - _line = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!' - - # Constructor to use for the test data (._line is passed to this - # constructor) - constructor = str - - def setUp(self): - self._line = self.constructor(self._line) - self._lines = self.constructor((self._line + '\n') * 5) - self._fp = self.CLASS(self._lines) - - def test_reads(self): - eq = self.assertEqual - self.assertRaises(TypeError, self._fp.seek) - eq(self._fp.read(10), self._line[:10]) - eq(self._fp.readline(), self._line[10:] + '\n') - eq(len(self._fp.readlines(60)), 2) - - def test_writes(self): - f = self.CLASS() - self.assertRaises(TypeError, f.seek) - f.write(self._line[:6]) - f.seek(3) - f.write(self._line[20:26]) - f.write(self._line[52]) - self.assertEqual(f.getvalue(), 'abcuvwxyz!') - - def test_writelines(self): - f = self.CLASS() - f.writelines([self._line[0], self._line[1], self._line[2]]) - f.seek(0) - self.assertEqual(f.getvalue(), 'abc') - - def test_writelines_error(self): - def errorGen(): - yield 'a' - raise KeyboardInterrupt() - f = self.CLASS() - self.assertRaises(KeyboardInterrupt, f.writelines, errorGen()) - - def test_truncate(self): - eq = self.assertEqual - f = self.CLASS() - f.write(self._lines) - f.seek(10) - f.truncate() - eq(f.getvalue(), 'abcdefghij') - f.truncate(5) - eq(f.getvalue(), 'abcde') - f.write('xyz') - eq(f.getvalue(), 'abcdexyz') - self.assertRaises(ValueError, f.truncate, -1) - f.close() - self.assertRaises(ValueError, f.write, 'frobnitz') - - def test_closed_flag(self): - f = self.CLASS() - self.assertEqual(f.closed, False) - f.close() - self.assertEqual(f.closed, True) - f = self.CLASS(self.constructor("abc")) - self.assertEqual(f.closed, False) - f.close() - self.assertEqual(f.closed, True) - - def test_isatty(self): - f = self.CLASS() - self.assertRaises(TypeError, f.isatty, None) - self.assertEqual(f.isatty(), False) - f.close() - self.assertRaises(ValueError, f.isatty) - - def test_iterator(self): - eq = self.assertEqual - unless = self.failUnless - eq(iter(self._fp), self._fp) - # Does this object support the iteration protocol? - unless(hasattr(self._fp, '__iter__')) - unless(hasattr(self._fp, '__next__')) - i = 0 - for line in self._fp: - eq(line, self._line + '\n') - i += 1 - eq(i, 5) - self._fp.close() - self.assertRaises(StopIteration, next, self._fp) - -class TestioStringIO(TestGenericStringIO, unittest.TestCase): - CLASS = io.StringIO - - def test_unicode(self): - - if not test_support.have_unicode: return - - # The StringIO module also supports concatenating Unicode - # snippets to larger Unicode strings. This is tested by this - # method. Note that cStringIO does not support this extension. - - f = self.CLASS() - f.write(self._line[:6]) - f.seek(3) - f.write(str(self._line[20:26])) - f.write(str(self._line[52])) - s = f.getvalue() - self.assertEqual(s, str('abcuvwxyz!')) - self.assertEqual(type(s), str) - - -def test_main(): - test_support.run_unittest(TestioStringIO) - - -if __name__ == '__main__': - test_main() Index: Lib/test/output/test_profile =================================================================== --- Lib/test/output/test_profile (revision 59847) +++ Lib/test/output/test_profile (working copy) @@ -10,10 +10,10 @@ 12 0.000 0.000 0.012 0.001 :0(hasattr) 1 0.000 0.000 0.000 0.000 :0(setprofile) 1 0.000 0.000 1.000 1.000 :1() - 2 0.000 0.000 0.000 0.000 io.py:1213(flush) + 2 0.000 0.000 0.000 0.000 io.py:1255(flush) 1 0.000 0.000 0.000 0.000 io.py:269(flush) - 1 0.000 0.000 0.000 0.000 io.py:656(closed) - 1 0.000 0.000 0.000 0.000 io.py:874(flush) + 1 0.000 0.000 0.000 0.000 io.py:659(closed) + 1 0.000 0.000 0.000 0.000 io.py:911(flush) 0 0.000 0.000 profile:0(profiler) 1 0.000 0.000 1.000 1.000 profile:0(testfunc()) 8 0.064 0.008 0.080 0.010 test_profile.py:103(subhelper) @@ -33,15 +33,15 @@ :0(append) -> :0(exc_info) -> :0(exec) -> :1()(1) 1.000 - io.py:1213(flush)(2) 0.000 + io.py:1255(flush)(2) 0.000 :0(hasattr) -> test_profile.py:115(__getattr__)(12) 0.028 :0(setprofile) -> :1() -> test_profile.py:30(testfunc)(1) 1.000 -io.py:1213(flush) -> io.py:269(flush)(1) 0.000 - io.py:874(flush)(1) 0.000 +io.py:1255(flush) -> io.py:269(flush)(1) 0.000 + io.py:911(flush)(1) 0.000 io.py:269(flush) -> -io.py:656(closed) -> -io.py:874(flush) -> io.py:656(closed)(1) 0.000 +io.py:659(closed) -> +io.py:911(flush) -> io.py:659(closed)(1) 0.000 profile:0(profiler) -> profile:0(testfunc())(1) 1.000 profile:0(testfunc()) -> :0(exec)(1) 1.000 :0(setprofile)(1) 0.000 @@ -74,10 +74,10 @@ test_profile.py:93(helper2)(8) 0.400 :0(setprofile) <- profile:0(testfunc())(1) 1.000 :1() <- :0(exec)(1) 1.000 -io.py:1213(flush) <- :0(exec)(2) 1.000 -io.py:269(flush) <- io.py:1213(flush)(1) 0.000 -io.py:656(closed) <- io.py:874(flush)(1) 0.000 -io.py:874(flush) <- io.py:1213(flush)(1) 0.000 +io.py:1255(flush) <- :0(exec)(2) 1.000 +io.py:269(flush) <- io.py:1255(flush)(1) 0.000 +io.py:659(closed) <- io.py:911(flush)(1) 0.000 +io.py:911(flush) <- io.py:1255(flush)(1) 0.000 profile:0(profiler) <- profile:0(testfunc()) <- profile:0(profiler)(1) 0.000 test_profile.py:103(subhelper) <- test_profile.py:93(helper2)(8) 0.400 Index: Lib/test/output/test_cProfile =================================================================== --- Lib/test/output/test_cProfile (revision 59847) +++ Lib/test/output/test_cProfile (working copy) @@ -5,10 +5,10 @@ ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 1.000 1.000 :1() - 2 0.000 0.000 0.000 0.000 io.py:1213(flush) + 2 0.000 0.000 0.000 0.000 io.py:1255(flush) 1 0.000 0.000 0.000 0.000 io.py:269(flush) - 1 0.000 0.000 0.000 0.000 io.py:656(closed) - 1 0.000 0.000 0.000 0.000 io.py:874(flush) + 1 0.000 0.000 0.000 0.000 io.py:659(closed) + 1 0.000 0.000 0.000 0.000 io.py:911(flush) 8 0.064 0.008 0.080 0.010 test_cProfile.py:103(subhelper) 28 0.028 0.001 0.028 0.001 test_cProfile.py:115(__getattr__) 1 0.270 0.270 1.000 1.000 test_cProfile.py:30(testfunc) @@ -30,11 +30,11 @@ Function called... ncalls tottime cumtime :1() -> 1 0.270 1.000 test_cProfile.py:30(testfunc) -io.py:1213(flush) -> 1 0.000 0.000 io.py:269(flush) - 1 0.000 0.000 io.py:874(flush) +io.py:1255(flush) -> 1 0.000 0.000 io.py:269(flush) + 1 0.000 0.000 io.py:911(flush) io.py:269(flush) -> -io.py:656(closed) -> -io.py:874(flush) -> 1 0.000 0.000 io.py:656(closed) +io.py:659(closed) -> +io.py:911(flush) -> 1 0.000 0.000 io.py:659(closed) test_cProfile.py:103(subhelper) -> 16 0.016 0.016 test_cProfile.py:115(__getattr__) test_cProfile.py:115(__getattr__) -> test_cProfile.py:30(testfunc) -> 1 0.014 0.130 test_cProfile.py:40(factorial) @@ -53,7 +53,7 @@ test_cProfile.py:93(helper2) -> 8 0.064 0.080 test_cProfile.py:103(subhelper) 8 0.000 0.008 {hasattr} {exec} -> 1 0.000 1.000 :1() - 2 0.000 0.000 io.py:1213(flush) + 2 0.000 0.000 io.py:1255(flush) {hasattr} -> 12 0.012 0.012 test_cProfile.py:115(__getattr__) {method 'append' of 'list' objects} -> {method 'disable' of '_lsprof.Profiler' objects} -> @@ -65,10 +65,10 @@ Function was called by... ncalls tottime cumtime :1() <- 1 0.000 1.000 {exec} -io.py:1213(flush) <- 2 0.000 0.000 {exec} -io.py:269(flush) <- 1 0.000 0.000 io.py:1213(flush) -io.py:656(closed) <- 1 0.000 0.000 io.py:874(flush) -io.py:874(flush) <- 1 0.000 0.000 io.py:1213(flush) +io.py:1255(flush) <- 2 0.000 0.000 {exec} +io.py:269(flush) <- 1 0.000 0.000 io.py:1255(flush) +io.py:659(closed) <- 1 0.000 0.000 io.py:911(flush) +io.py:911(flush) <- 1 0.000 0.000 io.py:1255(flush) test_cProfile.py:103(subhelper) <- 8 0.064 0.080 test_cProfile.py:93(helper2) test_cProfile.py:115(__getattr__) <- 16 0.016 0.016 test_cProfile.py:103(subhelper) 12 0.012 0.012 {hasattr} Index: Lib/test/test_largefile.py =================================================================== --- Lib/test/test_largefile.py (revision 59847) +++ Lib/test/test_largefile.py (working copy) @@ -152,7 +152,7 @@ newsize -= 1 f.seek(42) f.truncate(newsize) - expect(f.tell(), 42) # else pointer moved + expect(f.tell(), newsize) # pointer should be moved f.seek(0, 2) expect(f.tell(), newsize) # else wasn't truncated @@ -161,8 +161,8 @@ # cut it waaaaay back f.seek(0) f.truncate(1) - expect(f.tell(), 0) # else pointer moved - expect(len(f.read()), 1) # else wasn't truncated + expect(f.tell(), 1) # pointer should be moved + expect(len(f.read()), 0) finally: f.close() Index: Modules/_fileio.c =================================================================== --- Modules/_fileio.c (revision 59847) +++ Modules/_fileio.c (working copy) @@ -560,11 +560,10 @@ PyErr_SetString(PyExc_TypeError, "an integer is required"); return NULL; } -#if !defined(HAVE_LARGEFILE_SUPPORT) - pos = PyLong_AsLong(posobj); +#if defined(HAVE_LARGEFILE_SUPPORT) + pos = PyLong_AsLongLong(posobj); #else - pos = PyLong_Check(posobj) ? - PyLong_AsLongLong(posobj) : PyLong_AsLong(posobj); + pos = PyLong_AsLong(posobj); #endif if (PyErr_Occurred()) return NULL; @@ -580,10 +579,10 @@ if (res < 0) return PyErr_SetFromErrno(PyExc_IOError); -#if !defined(HAVE_LARGEFILE_SUPPORT) - return PyLong_FromLong(res); -#else +#if defined(HAVE_LARGEFILE_SUPPORT) return PyLong_FromLongLong(res); +#else + return PyLong_FromLong(res); #endif } @@ -630,49 +629,30 @@ return NULL; if (posobj == Py_None || posobj == NULL) { + /* Get the current position. */ posobj = portable_lseek(fd, NULL, 1); if (posobj == NULL) - return NULL; + return NULL; } else { - Py_INCREF(posobj); + /* Move to the position to be truncated. */ + posobj = portable_lseek(fd, posobj, 0); } -#if !defined(HAVE_LARGEFILE_SUPPORT) - pos = PyLong_AsLong(posobj); +#if defined(HAVE_LARGEFILE_SUPPORT) + pos = PyLong_AsLongLong(posobj); #else - pos = PyLong_Check(posobj) ? - PyLong_AsLongLong(posobj) : PyLong_AsLong(posobj); + pos = PyLong_AsLong(posobj); #endif - if (PyErr_Occurred()) { - Py_DECREF(posobj); + if (PyErr_Occurred()) return NULL; - } #ifdef MS_WINDOWS /* MS _chsize doesn't work if newsize doesn't fit in 32 bits, so don't even try using it. */ { HANDLE hFile; - PyObject *pos2, *oldposobj; - /* store the current position */ - oldposobj = portable_lseek(self->fd, NULL, 1); - if (oldposobj == NULL) { - Py_DECREF(posobj); - return NULL; - } - - /* Have to move current pos to desired endpoint on Windows. */ - errno = 0; - pos2 = portable_lseek(fd, posobj, SEEK_SET); - if (pos2 == NULL) { - Py_DECREF(posobj); - Py_DECREF(oldposobj); - return NULL; - } - Py_DECREF(pos2); - /* Truncate. Note that this may grow the file! */ Py_BEGIN_ALLOW_THREADS errno = 0; @@ -684,18 +664,6 @@ errno = EACCES; } Py_END_ALLOW_THREADS - - if (ret == 0) { - /* Move to the previous position in the file */ - pos2 = portable_lseek(fd, oldposobj, SEEK_SET); - if (pos2 == NULL) { - Py_DECREF(posobj); - Py_DECREF(oldposobj); - return NULL; - } - } - Py_DECREF(pos2); - Py_DECREF(oldposobj); } #else Py_BEGIN_ALLOW_THREADS @@ -705,7 +673,6 @@ #endif /* !MS_WINDOWS */ if (ret != 0) { - Py_DECREF(posobj); PyErr_SetFromErrno(PyExc_IOError); return NULL; } @@ -799,7 +766,8 @@ PyDoc_STRVAR(truncate_doc, "truncate([size: int]) -> None. Truncate the file to at most size bytes.\n" "\n" -"Size defaults to the current file position, as returned by tell()."); +"Size defaults to the current file position, as returned by tell()." +"The current file position is changed to the value of size."); #endif PyDoc_STRVAR(tell_doc,