Issue 8814: functools.wraps() did not copy __annotations__.

This commit is contained in:
Raymond Hettinger 2010-08-08 00:56:52 +00:00
parent f56c9cd30d
commit c6d80c1bef
5 changed files with 17 additions and 7 deletions

View file

@ -66,9 +66,9 @@ The :mod:`functools` module defines the following functions:
attributes of the wrapper function are updated with the corresponding attributes attributes of the wrapper function are updated with the corresponding attributes
from the original function. The default values for these arguments are the from the original function. The default values for these arguments are the
module level constants *WRAPPER_ASSIGNMENTS* (which assigns to the wrapper module level constants *WRAPPER_ASSIGNMENTS* (which assigns to the wrapper
function's *__name__*, *__module__* and *__doc__*, the documentation string) and function's *__name__*, *__module__*, *__annotations__* and *__doc__*, the
*WRAPPER_UPDATES* (which updates the wrapper function's *__dict__*, i.e. the documentation string) and *WRAPPER_UPDATES* (which updates the wrapper
instance dictionary). function's *__dict__*, i.e. the instance dictionary).
The main intended use for this function is in :term:`decorator` functions which 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 wrap the decorated function and return the wrapper. If the wrapper function is

View file

@ -12,7 +12,7 @@ from _functools import partial, reduce
# update_wrapper() and wraps() are tools to help write # update_wrapper() and wraps() are tools to help write
# wrapper functions that can handle naive introspection # wrapper functions that can handle naive introspection
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__', '__annotations__')
WRAPPER_UPDATES = ('__dict__',) WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper, def update_wrapper(wrapper,
wrapped, wrapped,
@ -30,6 +30,7 @@ def update_wrapper(wrapper,
function (defaults to functools.WRAPPER_UPDATES) function (defaults to functools.WRAPPER_UPDATES)
""" """
for attr in assigned: for attr in assigned:
if hasattr(wrapped, attr):
setattr(wrapper, attr, getattr(wrapped, attr)) setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated: for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {})) getattr(wrapper, attr).update(getattr(wrapped, attr, {}))

View file

@ -181,17 +181,19 @@ class TestUpdateWrapper(unittest.TestCase):
self.assertTrue(wrapped_attr[key] is wrapper_attr[key]) self.assertTrue(wrapped_attr[key] is wrapper_attr[key])
def test_default_update(self): def test_default_update(self):
def f(): def f(a:'This is a new annotation'):
"""This is a test""" """This is a test"""
pass pass
f.attr = 'This is also a test' f.attr = 'This is also a test'
def wrapper(): def wrapper(b:'This is the prior annotation'):
pass pass
functools.update_wrapper(wrapper, f) functools.update_wrapper(wrapper, f)
self.check_wrapper(wrapper, f) self.check_wrapper(wrapper, f)
self.assertEqual(wrapper.__name__, 'f') self.assertEqual(wrapper.__name__, 'f')
self.assertEqual(wrapper.__doc__, 'This is a test') self.assertEqual(wrapper.__doc__, 'This is a test')
self.assertEqual(wrapper.attr, 'This is also a test') self.assertEqual(wrapper.attr, 'This is also a test')
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
self.assertNotIn('b', wrapper.__annotations__)
def test_no_update(self): def test_no_update(self):
def f(): def f():
@ -204,6 +206,7 @@ class TestUpdateWrapper(unittest.TestCase):
self.check_wrapper(wrapper, f, (), ()) self.check_wrapper(wrapper, f, (), ())
self.assertEqual(wrapper.__name__, 'wrapper') self.assertEqual(wrapper.__name__, 'wrapper')
self.assertEqual(wrapper.__doc__, None) self.assertEqual(wrapper.__doc__, None)
self.assertEqual(wrapper.__annotations__, {})
self.assertFalse(hasattr(wrapper, 'attr')) self.assertFalse(hasattr(wrapper, 'attr'))
def test_selective_update(self): def test_selective_update(self):
@ -230,6 +233,7 @@ class TestUpdateWrapper(unittest.TestCase):
functools.update_wrapper(wrapper, max) functools.update_wrapper(wrapper, max)
self.assertEqual(wrapper.__name__, 'max') self.assertEqual(wrapper.__name__, 'max')
self.assertTrue(wrapper.__doc__.startswith('max(')) self.assertTrue(wrapper.__doc__.startswith('max('))
self.assertEqual(wrapper.__annotations__, {})
class TestWraps(TestUpdateWrapper): class TestWraps(TestUpdateWrapper):

View file

@ -144,6 +144,7 @@ Steve Clift
Nick Coghlan Nick Coghlan
Josh Cogliati Josh Cogliati
Dave Cole Dave Cole
Terrence Cole
Benjamin Collar Benjamin Collar
Jeffery Collins Jeffery Collins
Paul Colomiets Paul Colomiets

View file

@ -12,6 +12,10 @@ What's New in Python 3.1.3?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #8814: function annotations (the ``__annotations__`` attribute)
are now included in the set of attributes copied by default by
functools.wraps and functools.update_wrapper. Patch by Terrence Cole.
- Issue #83755: Implicit set-to-frozenset conversion was not thread-safe. - Issue #83755: Implicit set-to-frozenset conversion was not thread-safe.
- Issue #9416: Fix some issues with complex formatting where the - Issue #9416: Fix some issues with complex formatting where the