1

In trying to understand a conditional decorator in python I came upon this example. The accepted answer for that question explains how to define a conditional decorator, but not how to use it.

The example code is as follows:

class conditional_decorator(object):
    def __init__(self, dec, condition):
        self.decorator = dec
        self.condition = condition

    def __call__(self, func):
        if not self.condition:
            # Return the function unchanged, not decorated.
            return func
        return self.decorator(func)

@conditional_decorator(timeit, doing_performance_analysis)
def foo():
    time.sleep(2) 

But how to use it? I tried the following calls of foo like this:

doing_performance_analysis=False
foo()

doing_performance_analysis=True
foo()

but I got the following errors:

Traceback (most recent call last):
  File "tester.py", line 18, in <module>
    @conditional_decorator(timeit, doing_performance_analysis)
NameError: name 'doing_performance_analysis' is not defined

So how does it work correctly?

Alex
  • 41,580
  • 88
  • 260
  • 469
  • 2
    As Martijn says in his comments to that answer, that is only conditional in the sense that it is evaluated at *import* time. You can't change it at calling time. – Daniel Roseman Sep 27 '18 at 12:43
  • The error you get tells you you didn't set the name `doing_performance_analysis`. Have you tried defining that name to a boolean? – Martijn Pieters Sep 27 '18 at 12:45
  • I think I have set that name to a boolean, see my question. I am not sure what you mean otherwise... – Alex Sep 27 '18 at 12:46
  • 1
    You need to set it *at the time the decorator is applied*. That's why you get an error for the `@conditional_decorator()` line. – Martijn Pieters Sep 27 '18 at 12:47
  • So when I need to set the name at the beginning, I don't have a conditional decorator at all, but a static, unchangeable decorator... – Alex Sep 27 '18 at 12:48
  • That's still dynamic. The decorator is changes behaviour when applied, and ecorators are applied at the point of definition. Put the decorator on a nested function (inside another function) and it'll be executed each time the outer function is called. – Martijn Pieters Sep 27 '18 at 12:50
  • Ah ok. Thanks for that. But that is actually not what I want. I want to be able to change how a function is called during runtime... – Alex Sep 27 '18 at 12:51
  • What you want is a decorator that alters behaviour of the wrapper when called. That's a different kind of conditional behaviour. – Martijn Pieters Sep 27 '18 at 12:51
  • Decorators are *just syntactic sugar*. `@name` or `@name(...)` produces an object that is then called to pass in the decorated object. Whatever that call returns replaces the original decorated object. The question there wanted to know how to switch between returning the original object or something else. – Martijn Pieters Sep 27 '18 at 12:53
  • You just need to always (*unconditionally*) return something else, and that something else can make the decisions. – Martijn Pieters Sep 27 '18 at 12:53
  • Can you get me a link that describes more of that `different kind of conditional behaviour`? – Alex Sep 27 '18 at 13:00
  • @Alex: do you know how to write a regular decorator wrapper? That's usually just another function. Write a function that does what you want based on an extra parameter. – Martijn Pieters Sep 27 '18 at 13:02
  • But how to call that function then? `@conditional_decorator(parameter);foo()`? – Alex Sep 27 '18 at 13:03
  • I mean, I have to pass the extra parameter to something when I do a function call... – Alex Sep 27 '18 at 13:04
  • I want to leave the original function unchanged. – Alex Sep 27 '18 at 13:05

2 Answers2

0

You can make condition a function instead and make the decorator return a function wrapper so that it would evaluate your desired setting variable at run time.

from functools import wraps
class conditional_decorator(object):
    def __init__(self, dec, predicate):
        self.decorator = dec
        self.predicate = predicate

    def __call__(self, func):
        decorated_func = self.decorator(func)
        @wraps(func)
        def wrapper(*args, **kwargs):
            if self.predicate():
                return decorated_func(*args, **kwargs)
            return func(*args, **kwargs)
        return wrapper

@conditional_decorator(timeit, lambda: doing_performance_analysis)
def foo():
    time.sleep(2)

so that this will work as you intend:

doing_performance_analysis=False
foo()

doing_performance_analysis=True
foo()
Sam Mason
  • 15,216
  • 1
  • 41
  • 60
blhsing
  • 91,368
  • 6
  • 71
  • 106
0

If you use the python 3 wrapt module you can set an enabled flag to switch your decorator on or off. For debug reasons i try to paste a specific parameter given to a function to the clipboard (using pandas). This is done by the following decorator.

CLIPTRACE = True

def traceClipboard(fieldname):
    """ give fieldname of the functions formal parameter 
        to get value dumped to clipboard
        also use wrapt parameter to disable if cliptrace is not set
    """
    @wrapt.decorator(enabled=CLIPTRACE)
    def wrapper(wrapped, instance, args, kwargs):
        args_list = inspect.getfullargspec(wrapped)[0]
        if "self" in args_list:
            args_list.pop(0)
        if fieldname in args_list:
            pyperclip.copy(args[args_list.index(fieldname)])

        if fieldname in kwargs.keys():
            pyperclip.copy(kwargs.get(fieldname))
        return wrapped(*args, **kwargs)
    return wrapper

then use it for decorating a function of my session class:

 @traceClipboard("url")
 def _get(self, url, odata=None):
    """single _get request..."""

as long as CLIPTRACE is True the value of the parameter "url" is copied to clipboard, in productive environment CLIPTRACE is False and no clipboard copy is performed.

knobi
  • 762
  • 1
  • 6
  • 7