[Python-checkins] cpython (3.3): Issue #18879: When a method is looked up on a temporary file, avoid closing the

antoine.pitrou python-checkins at python.org
Sat Dec 21 22:19:21 CET 2013


http://hg.python.org/cpython/rev/f3b7a76fb778
changeset:   88113:f3b7a76fb778
branch:      3.3
parent:      88087:49dd8e1ef4c3
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Sat Dec 21 22:14:56 2013 +0100
summary:
  Issue #18879: When a method is looked up on a temporary file, avoid closing the file before the method is possibly called.

files:
  Lib/tempfile.py           |  102 +++++++++++++++++--------
  Lib/test/test_tempfile.py |   17 ++++
  Misc/NEWS                 |    3 +
  3 files changed, 87 insertions(+), 35 deletions(-)


diff --git a/Lib/tempfile.py b/Lib/tempfile.py
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -27,6 +27,7 @@
 
 # Imports.
 
+import functools as _functools
 import warnings as _warnings
 import sys as _sys
 import io as _io
@@ -349,13 +350,10 @@
                           "No usable temporary filename found")
 
 
-class _TemporaryFileWrapper:
-    """Temporary file wrapper
-
-    This class provides a wrapper around files opened for
-    temporary use.  In particular, it seeks to automatically
-    remove the file when it is no longer needed.
-    """
+class _TemporaryFileCloser:
+    """A separate object allowing proper closing of a temporary file's
+    underlying file object, without adding a __del__ method to the
+    temporary file."""
 
     def __init__(self, file, name, delete=True):
         self.file = file
@@ -363,26 +361,6 @@
         self.close_called = False
         self.delete = delete
 
-    def __getattr__(self, name):
-        # Attribute lookups are delegated to the underlying file
-        # and cached for non-numeric results
-        # (i.e. methods are cached, closed and friends are not)
-        file = self.__dict__['file']
-        a = getattr(file, name)
-        if not isinstance(a, int):
-            setattr(self, name, a)
-        return a
-
-    # The underlying __enter__ method returns the wrong object
-    # (self.file) so override it to return the wrapper
-    def __enter__(self):
-        self.file.__enter__()
-        return self
-
-    # iter() doesn't use __getattr__ to find the __iter__ method
-    def __iter__(self):
-        return iter(self.file)
-
     # NT provides delete-on-close as a primitive, so we don't need
     # the wrapper to do anything special.  We still use it so that
     # file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile.
@@ -401,18 +379,72 @@
                 if self.delete:
                     self.unlink(self.name)
 
+        # Need to ensure the file is deleted on __del__
         def __del__(self):
             self.close()
 
-        # Need to trap __exit__ as well to ensure the file gets
-        # deleted when used in a with statement
-        def __exit__(self, exc, value, tb):
-            result = self.file.__exit__(exc, value, tb)
-            self.close()
-            return result
     else:
-        def __exit__(self, exc, value, tb):
-            self.file.__exit__(exc, value, tb)
+        def close(self):
+            if not self.close_called:
+                self.close_called = True
+                self.file.close()
+
+
+class _TemporaryFileWrapper:
+    """Temporary file wrapper
+
+    This class provides a wrapper around files opened for
+    temporary use.  In particular, it seeks to automatically
+    remove the file when it is no longer needed.
+    """
+
+    def __init__(self, file, name, delete=True):
+        self.file = file
+        self.name = name
+        self.delete = delete
+        self._closer = _TemporaryFileCloser(file, name, delete)
+
+    def __getattr__(self, name):
+        # Attribute lookups are delegated to the underlying file
+        # and cached for non-numeric results
+        # (i.e. methods are cached, closed and friends are not)
+        file = self.__dict__['file']
+        a = getattr(file, name)
+        if hasattr(a, '__call__'):
+            func = a
+            @_functools.wraps(func)
+            def func_wrapper(*args, **kwargs):
+                return func(*args, **kwargs)
+            # Avoid closing the file as long as the wrapper is alive,
+            # see issue #18879.
+            func_wrapper._closer = self._closer
+            a = func_wrapper
+        if not isinstance(a, int):
+            setattr(self, name, a)
+        return a
+
+    # The underlying __enter__ method returns the wrong object
+    # (self.file) so override it to return the wrapper
+    def __enter__(self):
+        self.file.__enter__()
+        return self
+
+    # Need to trap __exit__ as well to ensure the file gets
+    # deleted when used in a with statement
+    def __exit__(self, exc, value, tb):
+        result = self.file.__exit__(exc, value, tb)
+        self.close()
+        return result
+
+    def close(self):
+        """
+        Close the temporary file, possibly deleting it.
+        """
+        self._closer.close()
+
+    # iter() doesn't use __getattr__ to find the __iter__ method
+    def __iter__(self):
+        return iter(self.file)
 
 
 def NamedTemporaryFile(mode='w+b', buffering=-1, encoding=None,
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -8,6 +8,7 @@
 import re
 import warnings
 import contextlib
+import weakref
 
 import unittest
 from test import support
@@ -674,6 +675,22 @@
         self.do_create(pre="a", suf="b")
         self.do_create(pre="aa", suf=".txt")
 
+    def test_method_lookup(self):
+        # Issue #18879: Looking up a temporary file method should keep it
+        # alive long enough.
+        f = self.do_create()
+        wr = weakref.ref(f)
+        write = f.write
+        write2 = f.write
+        del f
+        write(b'foo')
+        del write
+        write2(b'bar')
+        del write2
+        if support.check_impl_detail(cpython=True):
+            # No reference cycle was created.
+            self.assertIsNone(wr())
+
     def test_creates_named(self):
         # NamedTemporaryFile creates files with names
         f = tempfile.NamedTemporaryFile()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -29,6 +29,9 @@
 Library
 -------
 
+- Issue #18879: When a method is looked up on a temporary file, avoid closing
+  the file before the method is possibly called.
+
 - Issue #20034: Updated alias mapping to most recent locale.alias file
   from X.org distribution using makelocalealias.py.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list