How to strip decorators from a function in Python
There's been a bit of an update for this question. If you're using Python 3, you can use __wrapped__
property for decorators from stdlib.
Here's an example from Python Cookbook, 3rd edition, section 9.3 Unwrapping decorators
>>> @somedecorator
>>> def add(x, y):
... return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
>>>
If you are trying to unwrap a function from custom decorator, the decorator function needs to use wraps
function from functools
See discussion in Python Cookbook, 3rd edition, section 9.2 Preserving function metadata when writing decorators
>>> from functools import wraps
>>> def somedecorator(func):
... @wraps(func)
... def wrapper(*args, **kwargs):
... # decorator implementation here
... # ......
... return func(*args, **kwargs)
...
... return wrapper
In the general case, you can't, because
@with_connection
def spam(connection):
# Do something
is equivalent to
def spam(connection):
# Do something
spam = with_connection(spam)
which means that the "original" spam might not even exist anymore. A (not too pretty) hack would be this:
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
decorated._original = f
return decorated
@with_connection
def spam(connection):
# Do something
spam._original(testcon) # calls the undecorated function
balpha's solution can be made more generalizable with this meta-decorator:
def include_original(dec):
def meta_decorator(f):
decorated = dec(f)
decorated._original = f
return decorated
return meta_decorator
Then you can decorate your decorators with @include_original, and every one will have a testable (undecorated) version tucked away inside it.
@include_original
def shout(f):
def _():
string = f()
return string.upper()
return _
@shout
def function():
return "hello world"
>>> print function()
HELLO_WORLD
>>> print function._original()
hello world
Behold, FuglyHackThatWillWorkForYourExampleButICantPromiseAnythingElse:
orig_spam = spam.func_closure[0].cell_contents
Edit: For functions/methods decorated more than once and with more complicated decorators you can try using the following code. It relies on the fact, that decorated functions are __name__d differently than the original function.
def search_for_orig(decorated, orig_name):
for obj in (c.cell_contents for c in decorated.__closure__):
if hasattr(obj, "__name__") and obj.__name__ == orig_name:
return obj
if hasattr(obj, "__closure__") and obj.__closure__:
found = search_for_orig(obj, orig_name)
if found:
return found
return None
>>> search_for_orig(spam, "spam")
<function spam at 0x027ACD70>
It's not fool proof though. It will fail if the name of the function returned from a decorator is the same as the decorated one. The order of hasattr() checks is also a heuristic, there are decoration chains that return wrong results in any case.