[Python-checkins] r84132 - in python/branches/py3k: Doc/library/functools.rst Lib/functools.py Lib/test/test_functools.py Misc/NEWS

nick.coghlan python-checkins at python.org
Tue Aug 17 08:17:18 CEST 2010


Author: nick.coghlan
Date: Tue Aug 17 08:17:18 2010
New Revision: 84132

Log:
Document and test the resolution of issue 3445 (tolerate missing attributes in functools.update_wrapper, previously implemented as a side effect of the __annotations__ copying patch) and implement issue 9567 (add a __wrapped__ attribute when using update_wrapper)

Modified:
   python/branches/py3k/Doc/library/functools.rst
   python/branches/py3k/Lib/functools.py
   python/branches/py3k/Lib/test/test_functools.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Doc/library/functools.rst
==============================================================================
--- python/branches/py3k/Doc/library/functools.rst	(original)
+++ python/branches/py3k/Doc/library/functools.rst	Tue Aug 17 08:17:18 2010
@@ -54,6 +54,10 @@
    The wrapped function also has a :attr:`clear` attribute which can be
    called (with no arguments) to clear the cache.
 
+   The :attr:`__wrapped__` attribute may be used to access the original
+   function (e.g. to bypass the cache or to apply a different caching
+   strategy)
+
    A `LRU (least recently used) cache
    <http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used>`_
    is indicated when the pattern of calls changes over time, such as
@@ -141,12 +145,31 @@
    documentation string) and *WRAPPER_UPDATES* (which updates the wrapper
    function's *__dict__*, i.e. the instance dictionary).
 
+   To allow access to the original function for introspection and other purposes
+   (e.g. bypassing a caching decorator such as :func:`lru_cache`), this function
+   automatically adds a __wrapped__ attribute to the the wrapped that refers to
+   the original function.
+
    The main intended use for this function is in :term:`decorator` functions which
    wrap the decorated function and return the wrapper. If the wrapper function is
    not updated, the metadata of the returned function will reflect the wrapper
    definition rather than the original function definition, which is typically less
    than helpful.
 
+   :func:`update_wrapper` may be used with callables other than functions. Any
+   attributes named in *assigned* or *updated* that are missing from the object
+   being wrapped are ignored (i.e. this function will not attempt to set them
+   on the wrapper function). :exc:`AttributeError` is still raised if the
+   wrapper function itself is missing any attributes named in *updated*.
+
+   .. versionadded:: 3.2
+      Automatic addition of the __wrapped__ attribute
+
+   .. versionadded:: 3.2
+      Copying of the __annotations__ attribute by default
+
+   .. versionchanged:: 3.2
+      Missing attributes no longer trigger an AttributeError
 
 .. decorator:: wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
 

Modified: python/branches/py3k/Lib/functools.py
==============================================================================
--- python/branches/py3k/Lib/functools.py	(original)
+++ python/branches/py3k/Lib/functools.py	Tue Aug 17 08:17:18 2010
@@ -38,9 +38,14 @@
        are updated with the corresponding attribute from the wrapped
        function (defaults to functools.WRAPPER_UPDATES)
     """
+    wrapper.__wrapped__ = wrapped
     for attr in assigned:
-        if hasattr(wrapped, attr):
-            setattr(wrapper, attr, getattr(wrapped, attr))
+        try:
+            value = getattr(wrapped, attr)
+        except AttributeError:
+            pass
+        else:
+            setattr(wrapper, attr, value)
     for attr in updated:
         getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
     # Return the wrapper so this can be used as a decorator via partial()

Modified: python/branches/py3k/Lib/test/test_functools.py
==============================================================================
--- python/branches/py3k/Lib/test/test_functools.py	(original)
+++ python/branches/py3k/Lib/test/test_functools.py	Tue Aug 17 08:17:18 2010
@@ -194,6 +194,7 @@
     def test_default_update(self):
         wrapper, f = self._default_update()
         self.check_wrapper(wrapper, f)
+        self.assertIs(wrapper.__wrapped__, f)
         self.assertEqual(wrapper.__name__, 'f')
         self.assertEqual(wrapper.attr, 'This is also a test')
         self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
@@ -236,6 +237,28 @@
         self.assertEqual(wrapper.attr, 'This is a different test')
         self.assertEqual(wrapper.dict_attr, f.dict_attr)
 
+    def test_missing_attributes(self):
+        def f():
+            pass
+        def wrapper():
+            pass
+        wrapper.dict_attr = {}
+        assign = ('attr',)
+        update = ('dict_attr',)
+        # Missing attributes on wrapped object are ignored
+        functools.update_wrapper(wrapper, f, assign, update)
+        self.assertNotIn('attr', wrapper.__dict__)
+        self.assertEqual(wrapper.dict_attr, {})
+        # Wrapper must have expected attributes for updating
+        del wrapper.dict_attr
+        with self.assertRaises(AttributeError):
+            functools.update_wrapper(wrapper, f, assign, update)
+        wrapper.dict_attr = 1
+        with self.assertRaises(AttributeError):
+            functools.update_wrapper(wrapper, f, assign, update)
+
+    @unittest.skipIf(sys.flags.optimize >= 2,
+                     "Docstrings are omitted with -O2 and above")
     def test_builtin_update(self):
         # Test for bug #1576241
         def wrapper():
@@ -495,6 +518,12 @@
         self.assertEqual(f.hits, 0)
         self.assertEqual(f.misses, 1)
 
+        # Test bypassing the cache
+        self.assertIs(f.__wrapped__, orig)
+        f.__wrapped__(x, y)
+        self.assertEqual(f.hits, 0)
+        self.assertEqual(f.misses, 1)
+
         # test size zero (which means "never-cache")
         @functools.lru_cache(0)
         def f():

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Tue Aug 17 08:17:18 2010
@@ -90,6 +90,12 @@
 Library
 -------
 
+- Issue #9567: functools.update_wrapper now adds a __wrapped__ attribute
+  pointing to the original callable
+
+- Issue #3445: functools.update_wrapper now tolerates missing attributes
+  on wrapped callables
+
 - Issue #5867: Add abc.abstractclassmethod and abc.abstractstaticmethod.
 
 - Issue #9605: posix.getlogin() decodes the username with file filesystem


More information about the Python-checkins mailing list