Index: Include/abstract.h =================================================================== --- Include/abstract.h (revision 71503) +++ Include/abstract.h (working copy) @@ -307,6 +307,21 @@ Python expression: o.method(args). */ + PyAPI_FUNC(PyObject *) PyObject_CallSuperMethod(PyTypeObject *t, + PyObject *o, + char *method, + char *format, ...); + + /* + Construct a super instance for the given type and instance and + call the given method on it using a variable number of C arguments. + The C arguments are described by a mkvalue format string. + The format may be NULL, indicating that no arguments are provided. + Returns the result of the call on success, or NULL on failure. + This is the equivalent of the Python expression: + getattr(super(t, o), method)(*args). + */ + PyAPI_FUNC(PyObject *) _PyObject_CallFunction_SizeT(PyObject *callable, char *format, ...); PyAPI_FUNC(PyObject *) _PyObject_CallMethod_SizeT(PyObject *o, Index: Objects/abstract.c =================================================================== --- Objects/abstract.c (revision 71503) +++ Objects/abstract.c (working copy) @@ -2277,6 +2277,49 @@ } PyObject * +PyObject_CallSuperMethod(PyTypeObject *t, + PyObject *o, + char *method, + char *format, ...) +{ + va_list va; + PyObject *super = NULL; + PyObject *func = NULL; + PyObject *args; + PyObject *retval = NULL; + + if (t == NULL || o == NULL || method == NULL) + return null_error(); + + super = PyObject_CallFunction((PyObject *) &PySuper_Type, + "OO", t, o); + if (super == NULL) + goto exit; + + func = PyObject_GetAttrString(super, method); + if (func == NULL) { + PyErr_SetString(PyExc_AttributeError, method); + goto exit; + } + + if (format && *format) { + va_start(va, format); + args = Py_VaBuildValue(format, va); + va_end(va); + } + else + args = PyTuple_New(0); + + /* call_function_tail consumes args and can survive it being NULL. */ + retval = call_function_tail(func, args); + +exit: + Py_XDECREF(super); + Py_XDECREF(func); + return retval; +} + +PyObject * _PyObject_CallMethod_SizeT(PyObject *o, char *name, char *format, ...) { va_list va; Index: Lib/_pyio.py =================================================================== --- Lib/_pyio.py (revision 71503) +++ Lib/_pyio.py (working copy) @@ -12,6 +12,7 @@ except ImportError: from _dummy_thread import allocate_lock as Lock +import _io import io from io import __all__ from io import SEEK_SET, SEEK_CUR, SEEK_END @@ -328,8 +329,17 @@ self.flush() except IOError: pass # If flush() fails, just give up - self.__closed = True + self._close() + + def _close(self) -> None: + """Internal: close the IO object. + + This method may be overridden by subclasses but should never be called + by client code; it will be called implicitly by close. + """ + self.__closed = True + def __del__(self) -> None: """Destructor. Calls close().""" # The try/except block is in case this is called at program @@ -560,10 +570,145 @@ self._unsupported("write") io.RawIOBase.register(RawIOBase) -from _io import FileIO + +class FileIO(RawIOBase): + def __init__(self, file : str, mode : str = "r", closefd : bool =True): + """FileIO("example.txt") -> file IO object + + Open a file. The mode can be 'r', 'w' or 'a' for reading (default), + writing or appending. The file will be created if it doesn't exist + when opened for writing or appending; it will be truncated when + opened for writing. Add a '+' to the mode to allow simultaneous + reading and writing. + """ + # XXX(brian@sweetapp.com): Update the C and Python documentation to + # explain how the file argument can also be an int representing a file + # descriptor. + self._file = _io.FileIO(file, mode, closefd) + self.name = self._file.name + + def read(self, n: int = -1) -> bytes: + """Read and return up to n bytes. + + Returns an empty bytes object on EOF, or None if the object is + set not to block and has no data to read. + """ + return self._file.read(n) + + def readall(self): + """Read until EOF, using multiple read() call.""" + return self._file.readall() + + def readinto(self, b: bytearray) -> int: + """Read up to len(b) bytes into b. + + Returns number of bytes read (0 for EOF), or None if the object + is set not to block as has no data to read. + """ + return self._file.readinto(b) + + def write(self, b: bytes) -> int: + """Write the given buffer to the IO stream. + + Returns the number of bytes written, which may be less than len(b). + """ + return self._file.write(b) + + def seek(self, pos: int, whence: int = 0) -> int: + """Change stream position. + + Change the stream position to byte offset offset. offset is + interpreted relative to the position indicated by whence. Values + for whence are: + + * 0 -- start of stream (the default); offset should be zero or positive + * 1 -- current stream position; offset may be negative + * 2 -- end of stream; offset is usually negative + + Return the new absolute position. + """ + return self._file.seek(pos, whence) + + def tell(self) -> int: + """Return current stream position.""" + return self._file.tell() + + def truncate(self, pos: int = None) -> int: + """Truncate file to size bytes. + + Size defaults to the current IO position as reported by tell(). Return + the new size. + """ + return self._file.truncate(pos) + + ### Flush and close ### + + def flush(self) -> None: + self._file.flush() + super().flush() + + def _close(self) -> None: + self._file._close() + super()._close() + + ### Inquiries ### + + def seekable(self) -> bool: + """Return whether object supports random access. + + If False, seek(), tell() and truncate() will raise IOError. + This method may need to do a test seek(). + """ + return self._file.seekable() + + def readable(self) -> bool: + """Return whether object was opened for reading. + + If False, read() will raise IOError. + """ + return self._file.readable() + + def writable(self) -> bool: + """Return whether object was opened for writing. + + If False, write() and truncate() will raise IOError. + """ + return self._file.writable() + + @property + def closed(self): + """closed: bool. True iff the file has been closed. + + For backwards compatibility, this is a property, not a predicate. + """ + return self._file.closed + + @property + def closefd(self): + return self._file.closefd + + @property + def mode(self): + return self._file.mode + + ### Lower-level APIs ### + + def fileno(self) -> int: + """Returns underlying file descriptor if one exists. + + An IOError is raised if the IO object does not use a file descriptor. + """ + return self._file.fileno() + + def isatty(self) -> bool: + """Return whether this is an 'interactive' stream. + + Return False if it can't be determined. + """ + return self._file.isatty() + RawIOBase.register(FileIO) - class BufferedIOBase(IOBase): """Base class for buffered IO objects. @@ -684,14 +829,11 @@ def flush(self): self.raw.flush() + super().flush() - def close(self): - if not self.closed: - try: - self.flush() - except IOError: - pass # If flush() fails, just give up - self.raw.close() + def _close(self): + self.raw.close() + super()._close() ### Inquiries ### @@ -1022,6 +1164,7 @@ def flush(self): with self._write_lock: self._flush_unlocked() + super().flush() def _flush_unlocked(self): if self.closed: @@ -1104,11 +1247,13 @@ return self.writer.writable() def flush(self): - return self.writer.flush() + self.writer.flush() + super().flush() - def close(self): + def _close(self): self.writer.close() self.reader.close() + super()._close() def isatty(self): return self.reader.isatty() or self.writer.isatty() @@ -1434,13 +1579,11 @@ def flush(self): self.buffer.flush() self._telling = self._seekable + super().flush() - def close(self): - try: - self.flush() - except: - pass # If flush() fails, just give up + def _close(self): self.buffer.close() + super()._close() @property def closed(self): Index: Lib/test/test_io.py =================================================================== --- Lib/test/test_io.py (revision 71503) +++ Lib/test/test_io.py (working copy) @@ -492,19 +492,6 @@ file = self.open(f.fileno(), "r", closefd=False) self.assertEqual(file.buffer.raw.closefd, False) - def test_garbage_collection(self): - # FileIO objects are collected, and collecting them flushes - # all data to disk. - f = self.FileIO(support.TESTFN, "wb") - f.write(b"abcxxx") - f.f = f - wr = weakref.ref(f) - del f - gc.collect() - self.assert_(wr() is None, wr) - with open(support.TESTFN, "rb") as f: - self.assertEqual(f.read(), b"abcxxx") - def test_unbounded_file(self): # Issue #1174606: reading from an unbounded stream such as /dev/zero. zero = "/dev/zero" @@ -522,7 +509,19 @@ self.assertRaises(OverflowError, f.read) class CIOTest(IOTest): - pass + def test_garbage_collection(self): + # FileIO objects are collected, and collecting them flushes + # all data to disk. + # The Python version has __del__, so it ends into gc.garbage instead + f = self.FileIO(support.TESTFN, "wb") + f.write(b"abcxxx") + f.f = f + wr = weakref.ref(f) + del f + gc.collect() + self.assert_(wr() is None, wr) + with open(support.TESTFN, "rb") as f: + self.assertEqual(f.read(), b"abcxxx") class PyIOTest(IOTest): pass @@ -1985,7 +1984,82 @@ class PyIncrementalNewlineDecoderTest(IncrementalNewlineDecoderTest): pass +class CooperativeInheritanceTest(unittest.TestCase): + """Tests that calls propogate correctly through the class hierarchy.""" + def setUp(self): + support.unlink(support.TESTFN) + + def tearDown(self): + support.unlink(support.TESTFN) + + def _test_close_call_sequence(self, cls, init_args=()): + """Tests that the class "cls" propogates calls to its super classes.""" + on_flush = 1 + on_close = 2 + record = [] + + class CallChecker(self.IOBase): + def _close(s): + record.append(on_close) + super()._close() + def flush(s): + self.assertFalse(s.closed) + record.append(on_flush) + super().flush() + + class MyIOClass(cls, CallChecker): + pass + + f = MyIOClass(*init_args) + f.close() + self.assertTrue(f.closed) + self.assertEqual(record, [on_flush, on_close]) + + def test_FileIO(self): + self._test_close_call_sequence(self.FileIO, (support.TESTFN, "wb")) + + def test_StringIO(self): + self._test_close_call_sequence(self.StringIO) + + def test_BytesIO(self): + self._test_close_call_sequence(self.BytesIO) + + def test_BufferedReader(self): + self._test_close_call_sequence(self.BufferedReader, (self.BytesIO(),)) + + def test_BufferedWriter(self): + self._test_close_call_sequence(self.BufferedWriter, (self.BytesIO(),)) + + def test_BufferedRWPair(self): + self._test_close_call_sequence(self.BufferedRWPair, + (self.BytesIO(), self.BytesIO())) + + def test_TextIOWrapper(self): + self._test_close_call_sequence(self.TextIOWrapper, (self.BytesIO(),)) + +class PyCooperativeInheritanceTest(CooperativeInheritanceTest): + IOBase = pyio.IOBase + FileIO = pyio.FileIO + StringIO = pyio.StringIO + BytesIO = pyio.BytesIO + BufferedReader = pyio.BufferedReader + BufferedWriter = pyio.BufferedWriter + BufferedRWPair = pyio.BufferedRWPair + BufferedRandom = pyio.BufferedRandom + TextIOWrapper = pyio.TextIOWrapper + +class CCooperativeInheritanceTest(CooperativeInheritanceTest): + IOBase = io.IOBase + FileIO = io.FileIO + StringIO = io.StringIO + BytesIO = io.BytesIO + BufferedReader = io.BufferedReader + BufferedWriter = io.BufferedWriter + BufferedRWPair = io.BufferedRWPair + BufferedRandom = io.BufferedRandom + TextIOWrapper = io.TextIOWrapper + # XXX Tests for open() class MiscIOTest(unittest.TestCase): @@ -2139,6 +2213,7 @@ StatefulIncrementalDecoderTest, CIncrementalNewlineDecoderTest, PyIncrementalNewlineDecoderTest, CTextIOWrapperTest, PyTextIOWrapperTest, + CCooperativeInheritanceTest, PyCooperativeInheritanceTest, CMiscIOTest, PyMiscIOTest,) # Put the namespaces of the IO module we are testing and some useful mock Index: Modules/_io/bytesio.c =================================================================== --- Modules/_io/bytesio.c (revision 71503) +++ Modules/_io/bytesio.c (working copy) @@ -163,15 +163,6 @@ Py_RETURN_TRUE; } -PyDoc_STRVAR(flush_doc, -"flush() -> None. Does nothing."); - -static PyObject * -bytesio_flush(BytesIOObject *self) -{ - Py_RETURN_NONE; -} - PyDoc_STRVAR(getval_doc, "getvalue() -> bytes.\n" "\n" @@ -593,16 +584,29 @@ Py_RETURN_NONE; } -PyDoc_STRVAR(close_doc, -"close() -> None. Disable all I/O operations."); +PyDoc_STRVAR(_close_doc, +"Internal: close the IO object.\n" +"\n" +"This method may be overridden by subclasses but should never be called\n" +"by client code; it will be called implicitly by close."); static PyObject * -bytesio_close(BytesIOObject *self) +bytesio_close_(BytesIOObject *self) { + PyObject *ret; + if (self->buf != NULL) { PyMem_Free(self->buf); self->buf = NULL; } + + ret = PyObject_CallSuperMethod(&PyBytesIO_Type, + (PyObject *) self, + "_close", NULL); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + Py_RETURN_NONE; } @@ -691,8 +695,7 @@ {"readable", (PyCFunction)return_true, METH_NOARGS, NULL}, {"seekable", (PyCFunction)return_true, METH_NOARGS, NULL}, {"writable", (PyCFunction)return_true, METH_NOARGS, NULL}, - {"close", (PyCFunction)bytesio_close, METH_NOARGS, close_doc}, - {"flush", (PyCFunction)bytesio_flush, METH_NOARGS, flush_doc}, + {"_close", (PyCFunction)bytesio_close_, METH_NOARGS, _close_doc}, {"isatty", (PyCFunction)bytesio_isatty, METH_NOARGS, isatty_doc}, {"tell", (PyCFunction)bytesio_tell, METH_NOARGS, tell_doc}, {"write", (PyCFunction)bytesio_write, METH_O, write_doc}, Index: Modules/_io/stringio.c =================================================================== --- Modules/_io/stringio.c (revision 71503) +++ Modules/_io/stringio.c (working copy) @@ -473,14 +473,16 @@ } PyDoc_STRVAR(stringio_close_doc, - "Close the IO object. Attempting any further operation after the\n" - "object is closed will raise a ValueError.\n" + "Internal: close the IO object.\n" "\n" - "This method has no effect if the file is already closed.\n"); + "This method may be overridden by subclasses but should never be called\n" + "by client code; it will be called implicitly by close.\n"); static PyObject * -stringio_close(StringIOObject *self) +stringio_close_(StringIOObject *self) { + PyObject *ret; + self->closed = 1; /* Free up some memory */ if (resize_buffer(self, 0) < 0) @@ -488,6 +490,14 @@ Py_CLEAR(self->readnl); Py_CLEAR(self->writenl); Py_CLEAR(self->decoder); + + ret = PyObject_CallSuperMethod(&PyStringIO_Type, + (PyObject *) self, + "_close", NULL); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + Py_RETURN_NONE; } @@ -695,7 +705,7 @@ } static struct PyMethodDef stringio_methods[] = { - {"close", (PyCFunction)stringio_close, METH_NOARGS, stringio_close_doc}, + {"_close", (PyCFunction)stringio_close_, METH_NOARGS, stringio_close_doc}, {"getvalue", (PyCFunction)stringio_getvalue, METH_VARARGS, stringio_getvalue_doc}, {"read", (PyCFunction)stringio_read, METH_VARARGS, stringio_read_doc}, {"readline", (PyCFunction)stringio_readline, METH_VARARGS, stringio_readline_doc}, Index: Modules/_io/_iomodule.h =================================================================== --- Modules/_io/_iomodule.h (revision 71503) +++ Modules/_io/_iomodule.h (working copy) @@ -124,6 +124,7 @@ #define IO_STATE IO_MOD_STATE(PyState_FindModule(&_PyIO_Module)) extern PyObject *_PyIO_str_close; +extern PyObject *_PyIO_str_close_; extern PyObject *_PyIO_str_closed; extern PyObject *_PyIO_str_decode; extern PyObject *_PyIO_str_encode; Index: Modules/_io/iobase.c =================================================================== --- Modules/_io/iobase.c (revision 71503) +++ Modules/_io/iobase.c (working copy) @@ -134,6 +134,12 @@ "\n" "This method has no effect if the file is already closed.\n"); +PyDoc_STRVAR(IOBase_inner_close_doc, + "Internal: close the IO object.\n" + "\n" + "This method may be overridden by subclasses but should never be called\n" + "by client code; it will be called implicitly by close.\n"); + static int IOBase_closed(PyObject *self) { @@ -168,9 +174,12 @@ Py_RETURN_NONE; } -/* XXX: IOBase thinks it has to maintain its own internal state in - `__IOBase_closed` and call flush() by itself, but it is redundant with - whatever behaviour a non-trivial derived class will implement. */ +static PyObject * +IOBase_close_(PyObject *self, PyObject *args) { + if (PyObject_SetAttrString(self, "__IOBase_closed", Py_True) < 0) + return NULL; + Py_RETURN_NONE; +} static PyObject * IOBase_close(PyObject *self, PyObject *args) @@ -181,7 +190,6 @@ Py_RETURN_NONE; res = PyObject_CallMethodObjArgs(self, _PyIO_str_flush, NULL); - PyObject_SetAttrString(self, "__IOBase_closed", Py_True); if (res == NULL) { /* If flush() fails, just give up */ if (PyErr_ExceptionMatches(PyExc_IOError)) @@ -190,6 +198,12 @@ return NULL; } Py_XDECREF(res); + + res = PyObject_CallMethodObjArgs(self, _PyIO_str_close_, NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + Py_RETURN_NONE; } @@ -675,6 +689,7 @@ {"truncate", IOBase_truncate, METH_VARARGS, IOBase_truncate_doc}, {"flush", IOBase_flush, METH_NOARGS, IOBase_flush_doc}, {"close", IOBase_close, METH_NOARGS, IOBase_close_doc}, + {"_close", IOBase_close_, METH_NOARGS, IOBase_inner_close_doc}, {"seekable", IOBase_seekable, METH_NOARGS, IOBase_seekable_doc}, {"readable", IOBase_readable, METH_NOARGS, IOBase_readable_doc}, Index: Modules/_io/fileio.c =================================================================== --- Modules/_io/fileio.c (revision 71503) +++ Modules/_io/fileio.c (working copy) @@ -98,8 +98,10 @@ } static PyObject * -fileio_close(PyFileIOObject *self) +fileio_close_(PyFileIOObject *self) { + PyObject *ret; + if (!self->closefd) { self->fd = -1; Py_RETURN_NONE; @@ -108,8 +110,14 @@ if (errno < 0) return NULL; - return PyObject_CallMethod((PyObject*)&PyRawIOBase_Type, - "close", "O", self); + ret = PyObject_CallSuperMethod(&PyFileIO_Type, + (PyObject *) self, + "_close", NULL); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + Py_RETURN_NONE; } static PyObject * @@ -868,10 +876,10 @@ PyDoc_STRVAR(fileio_doc, -"file(name: str[, mode: str]) -> file IO object\n" +"FileIO(file : str[, mode: str, closefd: bool]) -> file IO object\n" "\n" "Open a file. The mode can be 'r', 'w' or 'a' for reading (default),\n" -"writing or appending. The file will be created if it doesn't exist\n" +"writing or appending. The file will be created if it doesn't exist\n" "when opened for writing or appending; it will be truncated when\n" "opened for writing. Add a '+' to the mode to allow simultaneous\n" "reading and writing."); @@ -925,12 +933,13 @@ PyDoc_STRVAR(readinto_doc, "readinto() -> Same as RawIOBase.readinto()."); -PyDoc_STRVAR(close_doc, -"close() -> None. Close the file.\n" +PyDoc_STRVAR(_close_doc, +"Internal: close the file object.\n" "\n" -"A closed file cannot be used for further I/O operations. close() may be\n" -"called more than once without error. Changes the fileno to -1."); +"This method may be overridden by subclasses but should never be called by\n" +"client code; it will be called implicitly by close. Changes fileno to -1."); + PyDoc_STRVAR(isatty_doc, "isatty() -> bool. True if the file is connected to a tty device."); @@ -953,7 +962,7 @@ #ifdef HAVE_FTRUNCATE {"truncate", (PyCFunction)fileio_truncate, METH_VARARGS, truncate_doc}, #endif - {"close", (PyCFunction)fileio_close, METH_NOARGS, close_doc}, + {"_close", (PyCFunction)fileio_close_, METH_NOARGS, _close_doc}, {"seekable", (PyCFunction)fileio_seekable, METH_NOARGS, seekable_doc}, {"readable", (PyCFunction)fileio_readable, METH_NOARGS, readable_doc}, {"writable", (PyCFunction)fileio_writable, METH_NOARGS, writable_doc}, Index: Modules/_io/bufferedio.c =================================================================== --- Modules/_io/bufferedio.c (revision 71503) +++ Modules/_io/bufferedio.c (working copy) @@ -368,8 +368,23 @@ static PyObject * BufferedIOMixin_flush(BufferedObject *self, PyObject *args) { + PyObject *res; + CHECK_INITIALIZED(self) - return PyObject_CallMethodObjArgs(self->raw, _PyIO_str_flush, NULL); + + res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_flush, NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + + res = PyObject_CallSuperMethod(&PyBufferedIOBase_Type, + (PyObject *) self, + "flush", NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + + Py_RETURN_NONE; } static int @@ -394,7 +409,7 @@ } static PyObject * -BufferedIOMixin_close(BufferedObject *self, PyObject *args) +BufferedIOMixin_close_(BufferedObject *self, PyObject *args) { PyObject *res = NULL; int r; @@ -410,21 +425,21 @@ Py_INCREF(res); goto end; } - /* flush() will most probably re-take the lock, so drop it first */ - LEAVE_BUFFERED(self) - res = PyObject_CallMethodObjArgs((PyObject *)self, _PyIO_str_flush, NULL); - ENTER_BUFFERED(self) - if (res == NULL) { - /* If flush() fails, just give up */ - if (PyErr_ExceptionMatches(PyExc_IOError)) - PyErr_Clear(); - else - goto end; - } - Py_XDECREF(res); res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_close, NULL); + if (res == NULL) + goto end; + Py_DECREF(res); + LEAVE_BUFFERED(self) + res = PyObject_CallSuperMethod(&PyBufferedIOBase_Type, + (PyObject *) self, + "_close", NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + + Py_RETURN_NONE; end: LEAVE_BUFFERED(self) return res; @@ -639,7 +654,18 @@ } LEAVE_BUFFERED(self) - return res; + if (res == NULL) + return NULL; + Py_DECREF(res); + + res = PyObject_CallSuperMethod(&PyBufferedIOBase_Type, + (PyObject *) self, + "flush", NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + + Py_RETURN_NONE; } static PyObject * @@ -1388,7 +1414,7 @@ static PyMethodDef BufferedReader_methods[] = { /* BufferedIOMixin methods */ {"flush", (PyCFunction)BufferedIOMixin_flush, METH_NOARGS}, - {"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS}, + {"_close", (PyCFunction)BufferedIOMixin_close_, METH_NOARGS}, {"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS}, {"readable", (PyCFunction)BufferedIOMixin_readable, METH_NOARGS}, {"writable", (PyCFunction)BufferedIOMixin_writable, METH_NOARGS}, @@ -1744,7 +1770,7 @@ static PyMethodDef BufferedWriter_methods[] = { /* BufferedIOMixin methods */ - {"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS}, + {"_close", (PyCFunction)BufferedIOMixin_close_, METH_NOARGS}, {"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS}, {"readable", (PyCFunction)BufferedIOMixin_readable, METH_NOARGS}, {"writable", (PyCFunction)BufferedIOMixin_writable, METH_NOARGS}, @@ -1960,7 +1986,20 @@ static PyObject * BufferedRWPair_flush(BufferedRWPairObject *self, PyObject *args) { - return _forward_call(self->writer, "flush", args); + PyObject *ret = _forward_call(self->writer, "flush", args); + + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + ret = PyObject_CallSuperMethod(&PyBufferedRWPair_Type, + (PyObject *) self, + "flush", NULL); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + Py_RETURN_NONE; } static PyObject * @@ -1976,14 +2015,26 @@ } static PyObject * -BufferedRWPair_close(BufferedRWPairObject *self, PyObject *args) +BufferedRWPair_close_(BufferedRWPairObject *self, PyObject *args) { PyObject *ret = _forward_call(self->writer, "close", args); if (ret == NULL) return NULL; Py_DECREF(ret); - return _forward_call(self->reader, "close", args); + ret = _forward_call(self->reader, "close", args); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + ret = PyObject_CallSuperMethod(&PyBufferedRWPair_Type, + (PyObject *) self, + "_close", NULL); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + Py_RETURN_NONE; } static PyObject * @@ -2013,7 +2064,7 @@ {"readable", (PyCFunction)BufferedRWPair_readable, METH_NOARGS}, {"writable", (PyCFunction)BufferedRWPair_writable, METH_NOARGS}, - {"close", (PyCFunction)BufferedRWPair_close, METH_NOARGS}, + {"_close", (PyCFunction)BufferedRWPair_close_, METH_NOARGS}, {"isatty", (PyCFunction)BufferedRWPair_isatty, METH_NOARGS}, {NULL, NULL} @@ -2122,7 +2173,7 @@ static PyMethodDef BufferedRandom_methods[] = { /* BufferedIOMixin methods */ - {"close", (PyCFunction)BufferedIOMixin_close, METH_NOARGS}, + {"_close", (PyCFunction)BufferedIOMixin_close_, METH_NOARGS}, {"seekable", (PyCFunction)BufferedIOMixin_seekable, METH_NOARGS}, {"readable", (PyCFunction)BufferedIOMixin_readable, METH_NOARGS}, {"writable", (PyCFunction)BufferedIOMixin_writable, METH_NOARGS}, Index: Modules/_io/_iomodule.c =================================================================== --- Modules/_io/_iomodule.c (revision 71503) +++ Modules/_io/_iomodule.c (working copy) @@ -24,6 +24,7 @@ /* Various interned strings */ PyObject *_PyIO_str_close; +PyObject *_PyIO_str_close_; PyObject *_PyIO_str_closed; PyObject *_PyIO_str_decode; PyObject *_PyIO_str_encode; @@ -700,6 +701,8 @@ /* Interned strings */ if (!(_PyIO_str_close = PyUnicode_InternFromString("close"))) goto fail; + if (!(_PyIO_str_close_ = PyUnicode_InternFromString("_close"))) + goto fail; if (!(_PyIO_str_closed = PyUnicode_InternFromString("closed"))) goto fail; if (!(_PyIO_str_decode = PyUnicode_InternFromString("decode"))) Index: Modules/_io/textio.c =================================================================== --- Modules/_io/textio.c (revision 71503) +++ Modules/_io/textio.c (working copy) @@ -2224,28 +2224,39 @@ static PyObject * TextIOWrapper_flush(PyTextIOWrapperObject *self, PyObject *args) { + PyObject *res; + CHECK_INITIALIZED(self); CHECK_CLOSED(self); self->telling = self->seekable; if (_TextIOWrapper_writeflush(self) < 0) return NULL; - return PyObject_CallMethod(self->buffer, "flush", NULL); + + res = PyObject_CallMethod(self->buffer, "flush", NULL); + if (res == NULL) + return NULL; + else + Py_DECREF(res); + + return PyObject_CallSuperMethod(&PyTextIOWrapper_Type, + (PyObject *) self, + "flush", NULL); } static PyObject * -TextIOWrapper_close(PyTextIOWrapperObject *self, PyObject *args) +TextIOWrapper_close_(PyTextIOWrapperObject *self, PyObject *args) { PyObject *res; CHECK_INITIALIZED(self); - res = PyObject_CallMethod((PyObject *)self, "flush", NULL); - if (res == NULL) { - /* If flush() fails, just give up */ - PyErr_Clear(); - } - else - Py_DECREF(res); - return PyObject_CallMethod(self->buffer, "close", NULL); + res = PyObject_CallMethod(self->buffer, "close", NULL); + if (res == NULL) + return NULL; + Py_DECREF(res); + + return PyObject_CallSuperMethod(&PyTextIOWrapper_Type, + (PyObject *) self, + "_close", NULL); } static PyObject * @@ -2345,7 +2356,7 @@ {"read", (PyCFunction)TextIOWrapper_read, METH_VARARGS}, {"readline", (PyCFunction)TextIOWrapper_readline, METH_VARARGS}, {"flush", (PyCFunction)TextIOWrapper_flush, METH_NOARGS}, - {"close", (PyCFunction)TextIOWrapper_close, METH_NOARGS}, + {"_close", (PyCFunction)TextIOWrapper_close_, METH_NOARGS}, {"fileno", (PyCFunction)TextIOWrapper_fileno, METH_NOARGS}, {"seekable", (PyCFunction)TextIOWrapper_seekable, METH_NOARGS},