This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: Add inspect.unwrap(f) to easily unravel "__wrapped__" chains
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.4
Status: closed Resolution: fixed
Dependencies: 17482 Superseder:
Assigned To: ncoghlan Nosy List: aliles, daniel.urban, eric.araujo, ezio.melotti, flox, jcea, meador.inge, ncoghlan, python-dev, rhettinger
Priority: normal Keywords: patch

Created on 2011-10-26 06:43 by ncoghlan, last changed 2022-04-11 14:57 by admin. This issue is now closed.

File name Uploaded Description Edit
inspect_unwrap.patch daniel.urban, 2012-04-25 20:34 inspect.unwrap - 1st patch review
p13266-2.diff aliles, 2012-10-30 04:28 Updated patch for 3.4 with documentation review
inspect_unwrap_3.patch daniel.urban, 2012-11-04 09:39 review
issue13266_inspect_unwrap_4.diff ncoghlan, 2013-07-15 12:55 Patch with ability to stop unwrapping early review
Messages (9)
msg146420 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2011-10-26 06:43
I just got bitten by the singularly unhelpful results of doing inspect.getsource(generator_context_manager).

Now that @functools.wraps adds the __wrapped__ attribute, perhaps inspect.getsource(f) should follow the wrapper chain by default?

This would affect other inspect APIs as well, such as getgeneratorstate() and the new getclosurevars() and getgeneratorlocals() that will be available in 3.3

While the source code of the wrapper could still be accessed by doing inspect.getsource(f.__code__), this isn't a reliable alternative in general (e.g. it doesn't work when introspecting closure state, since that needs access to the function object to look things up correctly). Accordingly, there would need to be a way to disable the "follow wrapper chains behaviour".

Alternatively (and more simply), we could just add an "inspect.unwrap(f)" function that followed a chain of __wrapped__ attributes and returned the last object in the chain (using an "already seen" set to avoid hit the recursion limit if someone sets up a wrapper loop). Applying this would then be a manual process when the initial output of an inspect function is clearly coming from a wrapper rather than the underlying function definition.
msg146421 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2011-10-26 06:56
After a little thought, I think the explicit "unwrap" function is the only viable approach. Doing the unwrapping implicitly just has too many nasty corner cases to track down to ensure we aren't losing existing functionality.

I'd also suggest that we add a __wrapped__ alias for the 'func' attribute of functools.partial objects.
msg159336 - (view) Author: Daniel Urban (daniel.urban) * (Python triager) Date: 2012-04-25 20:34
I've attached a patch implementing inspect.unwrap (+ some tests).
msg174179 - (view) Author: Aaron Iles (aliles) * Date: 2012-10-30 04:28
I've updated the patch for the current default branch (to be Python 3.4) and added documentation to the inspect module for the new unwraps function. Functionally unwraps and it's tests are unchanged.
msg174767 - (view) Author: Daniel Urban (daniel.urban) * (Python triager) Date: 2012-11-04 09:39
I've attached a patch addressing the comments on Rietveld. I've added another modification: inspect.signature uses inspect.unwrap. (It already tried to unwrap the function, but it wasn't protected from infinite recursion. I don't know if this worth fixing in 3.3.)
msg184655 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-03-19 18:16
Turns out there's a bug in the implementation of functools.update_wrapper :P

Added that as a dependency, since this API doesn't make sense until update_wrapper is behaving itself.

The new tests didn't pick it up because they don't use wraps or update_wrapper, they set __wrapped__ directly.

Also, the replacement of the recursion in inspect.signature is incorrect - we want to interleave checks for __signature__ as we recurse through the stack of wrapper functions.
msg193095 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-07-15 12:55
Added a version that allows the iteration to be terminated early if certain criteria are met, which is needed for a robust implementation of inspect.signature.

However, I'm thinking the callback based approach in this version isn't especially Pythonic, so I'm thinking it may be better to change the API to a generator function. That way the iterator can still take care of the wrapper loop detection, without needing the clumsy predicate API for early termination.

Instead, you would just use an ordinary search loop, and if you wanted the innermost function unconditionally you could do something like:

    for f in functools.unwrap(original): pass
    # f is now the innermost function
msg193165 - (view) Author: Aaron Iles (aliles) * Date: 2013-07-16 12:49
My +1 is for the callback based approach. The brevity of the search loop for finding the innermost function is (in my opinion at least) non-obvious, relying on for loops not having their own scope as it does.

If a generator based API was adopted instead, I propose a convenience function (unwrap_all?) to help developers avoid writing code like:

    inner = None
    for inner in functools.unwrap(outer):
    if inner is None:
        inner = outer

Which combines a misunderstanding of the API with for loop scope shortcut.
msg193814 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2013-07-28 10:00
New changeset 2aa6c1e35b8a by Nick Coghlan in branch 'default':
Close #13266: Add inspect.unwrap
Date User Action Args
2022-04-11 14:57:23adminsetgithub: 57475
2013-07-28 10:00:30python-devsetstatus: open -> closed

nosy: + python-dev
messages: + msg193814

resolution: fixed
stage: patch review -> resolved
2013-07-16 12:49:25alilessetmessages: + msg193165
2013-07-15 12:55:32ncoghlansetfiles: + issue13266_inspect_unwrap_4.diff

messages: + msg193095
2013-05-24 13:26:59ncoghlansetassignee: ncoghlan
2013-03-25 03:16:42jceasetnosy: + jcea
2013-03-19 18:16:05ncoghlansetdependencies: + functools.update_wrapper mishandles __wrapped__
messages: + msg184655
2012-11-22 17:34:37daniel.urbansetstage: needs patch -> patch review
2012-11-04 09:39:53daniel.urbansetfiles: + inspect_unwrap_3.patch

messages: + msg174767
2012-10-30 04:28:06alilessetfiles: + p13266-2.diff

messages: + msg174179
2012-08-29 11:41:49alilessetnosy: + aliles
2012-08-28 14:37:49eric.araujosetversions: + Python 3.4, - Python 3.3
2012-04-25 20:34:17daniel.urbansetfiles: + inspect_unwrap.patch
keywords: + patch
messages: + msg159336
2011-10-27 15:38:29eric.araujosetnosy: + rhettinger, eric.araujo
2011-10-27 14:08:57meador.ingesetnosy: + meador.inge
2011-10-26 16:46:58daniel.urbansetnosy: + daniel.urban
2011-10-26 13:43:29floxsetnosy: + flox
2011-10-26 06:56:37ncoghlansetmessages: + msg146421
title: Support for unwrapping __wrapped__ functions in 'inspect' module -> Add inspect.unwrap(f) to easily unravel "__wrapped__" chains
2011-10-26 06:44:47ezio.melottisetnosy: + ezio.melotti

type: enhancement
stage: needs patch
2011-10-26 06:43:11ncoghlancreate