diff options
author | Nick Coghlan <ncoghlan@gmail.com> | 2010-08-17 06:17:18 (GMT) |
---|---|---|
committer | Nick Coghlan <ncoghlan@gmail.com> | 2010-08-17 06:17:18 (GMT) |
commit | 9887683f7407b2d4ec0e0ff1ff9b9a26137d0724 (patch) | |
tree | 07c1120d84c6a4f83ede41bccd2db5d08acb7ee4 /Lib | |
parent | 632a0c1476bce66a2226ce9b103d0ef96e739a2c (diff) | |
download | cpython-9887683f7407b2d4ec0e0ff1ff9b9a26137d0724.zip cpython-9887683f7407b2d4ec0e0ff1ff9b9a26137d0724.tar.gz cpython-9887683f7407b2d4ec0e0ff1ff9b9a26137d0724.tar.bz2 |
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)
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/functools.py | 9 | ||||
-rw-r--r-- | Lib/test/test_functools.py | 29 |
2 files changed, 36 insertions, 2 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index c2c2282..bd1334b 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -38,9 +38,14 @@ def update_wrapper(wrapper, 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() diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 2b6da64..211ef18 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -194,6 +194,7 @@ class TestUpdateWrapper(unittest.TestCase): 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 @@ class TestUpdateWrapper(unittest.TestCase): 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 @@ class TestLRU(unittest.TestCase): 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(): |