Kaydet (Commit) 560f7647 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

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.
üst 5626eec0
...@@ -165,9 +165,9 @@ The :mod:`functools` module defines the following functions: ...@@ -165,9 +165,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
......
...@@ -19,7 +19,7 @@ from operator import itemgetter ...@@ -19,7 +19,7 @@ from operator import itemgetter
# 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,
...@@ -37,7 +37,8 @@ def update_wrapper(wrapper, ...@@ -37,7 +37,8 @@ def update_wrapper(wrapper,
function (defaults to functools.WRAPPER_UPDATES) function (defaults to functools.WRAPPER_UPDATES)
""" """
for attr in assigned: for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr)) if hasattr(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, {}))
# Return the wrapper so this can be used as a decorator via partial() # Return the wrapper so this can be used as a decorator via partial()
......
...@@ -182,11 +182,11 @@ class TestUpdateWrapper(unittest.TestCase): ...@@ -182,11 +182,11 @@ class TestUpdateWrapper(unittest.TestCase):
self.assertTrue(wrapped_attr[key] is wrapper_attr[key]) self.assertTrue(wrapped_attr[key] is wrapper_attr[key])
def _default_update(self): def _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)
return wrapper, f return wrapper, f
...@@ -196,6 +196,8 @@ class TestUpdateWrapper(unittest.TestCase): ...@@ -196,6 +196,8 @@ class TestUpdateWrapper(unittest.TestCase):
self.check_wrapper(wrapper, f) self.check_wrapper(wrapper, f)
self.assertEqual(wrapper.__name__, 'f') self.assertEqual(wrapper.__name__, 'f')
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__)
@unittest.skipIf(sys.flags.optimize >= 2, @unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above") "Docstrings are omitted with -O2 and above")
...@@ -214,6 +216,7 @@ class TestUpdateWrapper(unittest.TestCase): ...@@ -214,6 +216,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):
...@@ -240,6 +243,7 @@ class TestUpdateWrapper(unittest.TestCase): ...@@ -240,6 +243,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):
......
...@@ -154,6 +154,7 @@ Steve Clift ...@@ -154,6 +154,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
Robert Collins Robert Collins
......
...@@ -37,6 +37,10 @@ Extensions ...@@ -37,6 +37,10 @@ Extensions
Library Library
------- -------
- 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 #2944: asyncore doesn't handle connection refused correctly. - Issue #2944: asyncore doesn't handle connection refused correctly.
- Issue #4184: Private attributes on smtpd.SMTPChannel made public and - Issue #4184: Private attributes on smtpd.SMTPChannel made public and
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment