How to Strip Decorators from a Function in Python

How to strip decorators from a function in Python

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

How to dynamically remove a decorator from a function?

Modern versions of functools.wraps install the original function as an attribute __wrapped__ on the wrappers they create. (One could search through __closure__ on the nested functions typically used for the purpose, but other types could be used as well.) It’s reasonable to expect whatever wrapper to follow this convention.

An alternative is to have a permanent wrapper that can be controlled by a flag, so that it can be enabled and disabled without removing and reinstating it. This has the advantage that the wrapper can keep its state (here, the cached values). The flag can be a separate variable (e.g., another attribute on an object bearing the wrapped function, if any) or can be an attribute on the wrapper itself.

How to remove the self argument from a decorator's arguments if there is one

The simplest way to achieve this is to explicitly tell your decorator that it's decorating a class method. Your decorator will take a keyword argument denoting that.

def decorator(is_classmethod=False):
def wrapper(func):
@functools.wraps(func)
def call(*args):
if is_classmethod:
print(args[1:]) # omit args[0]
else:
print(args)
return func(*args) # pass all the args to the function
return call
return wrapper

class cls:
@decorator(is_classmethod=True)
def meth(self, a):
pass

@decorator
def func(c):
pass

cls().meth("a")
func("c")

Since the user already knows what they're decorating, you can avoid complicated introspection code in the decorator, and give the user flexibility of processing the first argument or not.

please take a look at my answer to this SO question: Decorators with parameters? for a cleaner (IMHO) way to write decorators with parameters. There are some really good answers in that post.

remove decorator from stack trace

Finally found an answer that works (with a minor change, see below). Full credits to @Kyuuhachi!

This assumes that we are using CPython. The key part is to use the _testcapi module.

The difference with @Kyuuhachi's answer is minor: in our case, we only want to remove the decorator itself from the stack trace. In other words, we want the same stack trace as if the function had not been decorated.

The decorator in my question becomes:

import functools
import sys
import _testcapi

def bar(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# ... (decorator functionality before calling func)
try:
result = func(*args, **kwargs)
except:
tp, exc, tb = sys.exc_info()
_testcapi.set_exc_info(tp, exc, tb.tb_next)
del tp, exc, tb
raise

# ... (decorator functionality after calling func)
return result
return wrapper

@bar
def f(x):
return 1 / x

With that:

>>> f()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 f()

TypeError: f() missing 1 required positional argument: 'x'

and

---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 f(0)

Input In [1], in f(x)
21 @bar
22 def f(x):
---> 23 return 1 / x

ZeroDivisionError: division by zero

Which is exactly what I wanted.

Remove a value from a set with a function decorator

Here is a decorator that modifies a function which returns a set so that the set returned doesn't have the function arguments:

def proper(func):
def f(*args, **kwargs):
s = func(*args, **kwargs)
return s.difference(args)
return f

For example:

@proper

def factors(n):
return set(reduce(list.__add__,
([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))

Then:

>>> factors(10)
{1, 2, 5}

I haven't tested it with a function with more than one argument.

How to remove the effects of a decorator while testing in python?

The retry decorator you are using is built on top of the decorator.decorator utility decorator with a simpler fallback if that package is not installed.

The result has a __wrapped__ attribute that gives you access to the original function:

orig = _sftp_command_with_retries.__wrapped__

If decorator is not installed and you are using a Python version before 3.2, that attribute won't be present; you'd have to manually reach into the decorator closure:

orig = _sftp_command_with_retries.__closure__[1].cell_contents

(the closure at index 0 is the retry_decorator produced when calling retry() itself).

Note that decorator is listed as a dependency in the retry package metadata, and if you installed it with pip the decorator package would have been installed automatically.

You can support both possibilities with a try...except:

try:
orig = _sftp_command_with_retries.__wrapped__
except AttributeError:
# decorator.decorator not available and not Python 3.2 or newer.
orig = _sftp_command_with_retries.__closure__[1].cell_contents

Note that you always can patch time.sleep() with a mock. The decorator code will use the mock as it references the 'global' time module in the module source code.

Alternatively, you could patch retry.api.__retry_internal with:

import retry.api
def dontretry(f, *args, **kw):
return f()

with mock.patch.object(retry.api, '__retry_internal', dontretry):
# use your decorated method

This temporarily replaces the function that does the actual retrying with one that just calls your original function directly.

How to bypass pytest fixture decorator?

Applying any trick that bypasses the decorator has the same value as decrementing a number after incrementing it. The cleanest solution would be to define two functions:

def plain_function():
# call this function when you need to bypass decorator
pass

@pytest.fixture
def simlar_fixture():
return plain_function()

Introspection to get decorator names on a method?

If you can change the way you call the decorators from

class Foo(object):
@many
@decorators
@here
def bar(self):
pass

to

class Foo(object):
@register(many,decos,here)
def bar(self):
pass

then you could register the decorators this way:

def register(*decorators):
def register_wrapper(func):
for deco in decorators[::-1]:
func=deco(func)
func._decorators=decorators
return func
return register_wrapper

For example:

def many(f):
def wrapper(*args,**kwds):
return f(*args,**kwds)
return wrapper

decos = here = many

class Foo(object):
@register(many,decos,here)
def bar(self):
pass

foo=Foo()

Here we access the tuple of decorators:

print(foo.bar._decorators)
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>)

Here we print just the names of the decorators:

print([d.func_name for d in foo.bar._decorators])
# ['many', 'decos', 'here']

Deactivate function with decorator

You can turn functions into no-ops (that log a warning) with a decorator:

def conditional(cond, warning=None):
def noop_decorator(func):
return func # pass through

def neutered_function(func):
def neutered(*args, **kw):
if warning:
log.warn(warning)
return
return neutered

return noop_decorator if cond else neutered_function

Here conditional is a decorator factory. It returns one of two decorators depending on the condition.

One decorator simply leaves the function untouched. The other decorator replaces the decorated function altogether, with one that issues a warning instead.

Use:

@conditional('matplotlib' in sys.modules, 'Please install matplotlib')
def foo(self, bar):
pass


Related Topics



Leave a reply



Submit