Index: Lib/test/test_functools.py =================================================================== --- Lib/test/test_functools.py (revision 77513) +++ Lib/test/test_functools.py (working copy) @@ -266,7 +266,24 @@ self.assertEqual(wrapper.attr, 'This is a different test') self.assertEqual(wrapper.dict_attr, f.dict_attr) + def test_wraps_missing_attributes(self): + def testme(): + pass + + # sanity check + self.assert_(not hasattr(testme, 'contrived_attribute')) + + assigned = functools.WRAPPER_ASSIGNMENTS + ('contrived_attribute',) + @functools.wraps(testme, assigned=assigned) + def wrapper(): + pass + + # if everything worked as expected, @functools.wraps will have copied + # over the usual attribute assignments, and silently skipped the missing + # "contrived_attribute" attribute + self.check_wrapper(wrapper, testme, assigned=functools.WRAPPER_ASSIGNMENTS) + class TestReduce(unittest.TestCase): def test_reduce(self): Index: Lib/functools.py =================================================================== --- Lib/functools.py (revision 77513) +++ Lib/functools.py (working copy) @@ -30,7 +30,8 @@ function (defaults to functools.WRAPPER_UPDATES) """ for attr in assigned: - setattr(wrapper, attr, getattr(wrapped, attr)) + if hasattr(wrapped, attr): + setattr(wrapper, attr, getattr(wrapped, attr)) for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) # Return the wrapper so this can be used as a decorator via partial()