diff -r 5fea362b92fc Lib/inspect.py --- a/Lib/inspect.py Wed Apr 25 19:45:11 2012 +0200 +++ b/Lib/inspect.py Wed Apr 25 22:30:46 2012 +0200 @@ -1217,3 +1217,19 @@ if generator.gi_frame.f_lasti == -1: return GEN_CREATED return GEN_SUSPENDED + +def unwrap(func): + """Get the object wrapped by 'func'. + + Follow the chain of __wrapped__ attributes, + and return the last object in the chain. + If there is a cycle, raise a ValueError. + """ + memo = {id(func)} + while hasattr(func, '__wrapped__'): + func = func.__wrapped__ + if id(func) in memo: + raise ValueError('wrapper loop') + else: + memo.add(id(func)) + return func diff -r 5fea362b92fc Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Wed Apr 25 19:45:11 2012 +0200 +++ b/Lib/test/test_inspect.py Wed Apr 25 22:30:46 2012 +0200 @@ -8,6 +8,7 @@ import collections import os import shutil +import functools from os.path import normcase from test.support import run_unittest, TESTFN, DirsOnSysPath @@ -1170,13 +1171,44 @@ self.assertIn(name, str(state)) +class TestUnwrap(unittest.TestCase): + + def test_unwrap(self): + def func(a, b): + return a + b + wrapper = functools.lru_cache(maxsize=20)(func) + self.assertIs(inspect.unwrap(wrapper), func) + + def test_cycle(self): + def func1(): pass + func1.__wrapped__ = func1 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + + def func2(): pass + func2.__wrapped__ = func1 + func1.__wrapped__ = func2 + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func1) + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + inspect.unwrap(func2) + + def test_unhashable(self): + def func(): pass + func.__wrapped__ = None + class C: + __hash__ = None + __wrapped__ = func + self.assertIs(inspect.unwrap(C()), None) + + def test_main(): run_unittest( TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, - TestNoEOL + TestNoEOL, TestUnwrap ) if __name__ == "__main__":