How To Get Source Code Of Function That Is Wrapped By A Decorator?
Solution 1:
In Python 2, the @functools.wraps()
decorator does not set the convenience __wrapped__
attribute that the Python 3 version adds (new in Python 3.2).
This means you'll have to resort to extracting the original function from the closure. Exactly at what location will depend on the exact decorator implementation, but picking the first function object should be a good generalisation:
from types import FunctionType
defextract_wrapped(decorated):
closure = (c.cell_contents for c in decorated.__closure__)
returnnext((c for c in closure ifisinstance(c, FunctionType)), None)
Usage:
print inspect.getsource(extract_wrapped(my_func))
Demo using your sample:
>>> print inspect.getsource(extract_wrapped(my_func))
@my_decoratordefmy_func():
print"supposed to return this instead!"return
Another option is to update the functools
library to add a __wrapped__
attribute for you, the same way Python 3 does:
import functools
defadd_wrapped(uw):
@functools.wraps(uw)defupdate_wrapper(wrapper, wrapped, **kwargs):
wrapper = uw(wrapper, wrapped, **kwargs)
wrapper.__wrapped__ = wrapped
return wrapper
functools.update_wrapper = add_wrapped(functools.update_wrapper)
Run that code before importing the decorator you want to see affected (so they end up using the new version of functools.update_wrapper()
).
You'll have to manually unwrap still (the Python 2 inspect
module doesn't go looking for the attribute); here's a simple helper function do that:
def unwrap(func):
while hasattr(func, '__wrapped__'):
func=func.__wrapped__
returnfunc
This will unwrap any level of decorator wrapping. Or use a copy of the inspect.unwrap()
implementation from Python 3, which includes checking for accidental circular references.
Solution 2:
As Martijn Pieters points out in his answer, the Python 2 @functool.wraps()
decorator doesn't define a __wrapped__
attribute, which would make doing what you want to do very easy. According to the documentation I read, even though it was added in Python 3.2, there was a bug in the ways it was sometimes handled until version 3.4 was released—so the code below uses v3.4 as the cut-off for defining a custom wraps()
decorator.
Since from its name it sounds like you have control over my_decorator()
, you can workaround the issue by defining you're own wraps
-like function, rather than extracting the original function from the closure, as shown in his answer. Here's how to do it (which works in Python 2 and 3):
import functools
import inspect
import sys
if sys.version_info[0:2] >= (3, 4): # Python v3.4+?
wraps = functools.wraps # built-in has __wrapped__ attributeelse:
defwraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
defwrapper(f):
f = functools.wraps(wrapped, assigned, updated)(f)
f.__wrapped__ = wrapped # set attribute missing in earlier versionsreturn f
return wrapper
defmy_decorator(some_function):
@wraps(some_function)defwrapper():
some_function()
return wrapper
@my_decoratordefmy_func():
print("supposed to return this instead!")
returnprint(inspect.getsource(my_func.__wrapped__))
Output:
@my_decorator
def my_func():
print("supposed to return this instead!")
return
Post a Comment for "How To Get Source Code Of Function That Is Wrapped By A Decorator?"