diff -r 54d7e9919876 Lib/_pyio.py --- a/Lib/_pyio.py Thu Mar 24 13:55:58 2016 +0100 +++ b/Lib/_pyio.py Thu Mar 24 15:29:28 2016 +0100 @@ -313,6 +313,8 @@ class IOBase(metaclass=abc.ABCMeta): fp.write('Spam and eggs!') """ + _finalizing = False + ### Internal ### def _unsupported(self, name): @@ -375,14 +377,15 @@ class IOBase(metaclass=abc.ABCMeta): def __del__(self): """Destructor. Calls close().""" - # The try/except block is in case this is called at program - # exit time, when it's possible that globals have already been - # deleted, and then the close() call might fail. Since - # there's nothing we can do about such failures and they annoy - # the end users, we suppress the traceback. + # Signal close() that it was called as part + # of the object finalization process. + self._finalizing = True try: self.close() except: + # Silencing I/O errors is bad, but printing spurious tracebacks + # is equally as bad, and potentially more frequent (because of + # shutdown issues). pass ### Inquiries ### @@ -766,13 +769,25 @@ class _BufferedIOMixin(BufferedIOBase): raise ValueError("flush of closed file") self.raw.flush() + def _dealloc_warn(self, stacklevel=0): + if self.raw is None: + return + if not hasattr(self.raw, '_dealloc_warn'): + return + self.raw._dealloc_warn(1 + stacklevel) + def close(self): - if self.raw is not None and not self.closed: - try: - # may raise BlockingIOError or BrokenPipeError etc - self.flush() - finally: - self.raw.close() + if self.raw is None or self.closed: + return + + if self._finalizing: + self._dealloc_warn() + + try: + # may raise BlockingIOError or BrokenPipeError etc + self.flush() + finally: + self.raw.close() def detach(self): if self.raw is None: @@ -1510,12 +1525,9 @@ class FileIO(RawIOBase): raise self._fd = fd - def __del__(self): - if self._fd >= 0 and self._closefd and not self.closed: - import warnings - warnings.warn('unclosed file %r' % (self,), ResourceWarning, - stacklevel=2, source=self) - self.close() + @property + def closed(self): + return (self._fd < 0) def __getstate__(self): raise TypeError("cannot serialize '%s' object", self.__class__.__name__) @@ -1651,18 +1663,31 @@ class FileIO(RawIOBase): os.ftruncate(self._fd, size) return size + def _dealloc_warn(self, stacklevel=0): + import warnings + warnings.warn('unclosed file %r' % (self,), ResourceWarning, + stacklevel=4 + stacklevel, + source=self) + def close(self): """Close the file. A closed file cannot be used for further I/O operations. close() may be called more than once without error. """ - if not self.closed: - try: - if self._closefd: - os.close(self._fd) - finally: - super().close() + try: + super().close() + finally: + if not self._closefd: + self._fd = -1 + elif self._fd >= 0: + try: + if self._finalizing: + self._dealloc_warn() + finally: + fd = self._fd + self._fd = -1 + os.close(fd) def seekable(self): """True if file supports random-access.""" @@ -2025,6 +2050,9 @@ class TextIOWrapper(TextIOBase): def close(self): if self.buffer is not None and not self.closed: + if self._finalizing and hasattr(self.buffer, '_dealloc_warn'): + self.buffer._dealloc_warn(0) + try: self.flush() finally: