Rotary decorators

What's the best way to enable or disable decorators without thinking about each design and commenting on it? Let's say you have a comparison decoder:

# deco.py
def benchmark(func):
  def decorator():
    # fancy benchmarking 
  return decorator

      

and in your module something like:

# mymodule.py
from deco import benchmark

class foo(object):
  @benchmark
  def f():
    # code

  @benchmark
  def g():
    # more code

      

That's good, but sometimes you don't need tests and don't need any overhead. I am doing the following. Add another decorator:

# anothermodule.py
def noop(func):
  # do nothing, just return the original function
  return func

      

And then comment out the import line and add another one:

# mymodule.py
#from deco import benchmark 
from anothermodule import noop as benchmark

      

The benchmarks are now switched on a per-file basis, and only to change the import statement in the module in question. Individual decorators can be controlled independently.

Is there a better way to do this? It would be nice not to edit the source file at all and specify which decorators to use in which files elsewhere.

+3


source to share


6 answers


You can add a condition to the decorator itself:



def benchmark(func):
    if not <config.use_benchmark>:
        return func
    def decorator():
    # fancy benchmarking 
    return decorator

      

+4


source


I used the following approach. This is almost identical to CaptainMurphy's suggestion, but it has the advantage that you don't need to call the decorator like a function.

import functools

class SwitchedDecorator:
    def __init__(self, enabled_func):
        self._enabled = False
        self._enabled_func = enabled_func

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, new_value):
        if not isinstance(new_value, bool):
            raise ValueError("enabled can only be set to a boolean value")
        self._enabled = new_value

    def __call__(self, target):
        if self._enabled:
            return self._enabled_func(target)
        return target


def deco_func(target):
    """This is the actual decorator function.  It written just like any other decorator."""
    def g(*args,**kwargs):
        print("your function has been wrapped")
        return target(*args,**kwargs)
    functools.update_wrapper(g, target)
    return g


# This is where we wrap our decorator in the SwitchedDecorator class.
my_decorator = SwitchedDecorator(deco_func)

# Now my_decorator functions just like the deco_func decorator,
# EXCEPT that we can turn it on and off.
my_decorator.enabled=True

@my_decorator
def example1():
    print("example1 function")

# we'll now disable my_decorator.  Any subsequent uses will not
# actually decorate the target function.
my_decorator.enabled=False
@my_decorator
def example2():
    print("example2 function")

      



In the above example, 1 will be styled and example 2 will NOT be styled. Whenever I need to enable or disable decorators modulo, I just have a function that creates a new SwitchedDecorator whenever I need another copy.

+2


source


Here's what I finally came up with for switching between modules. @Nneonneo's suggestion is used as a starting point.

Random modules use decorators as usual, unaware of switching.

foopkg.py:

from toggledeco import benchmark

@benchmark
def foo():
    print("function in foopkg")

      

barpkg.py:

from toggledeco import benchmark

@benchmark
def bar():
    print("function in barpkg")

      

The decorator module itself maintains a set of function references for all decorators that have been disabled, and each decorator checks for its existence in that set. If so, it just returns a raw function (no decorator). By default, the set is empty (all included).

toggledeco.py:

import functools

_disabled = set()
def disable(func):
    _disabled.add(func)
def enable(func):
    _disabled.discard(func)

def benchmark(func):
    if benchmark in _disabled:
        return func
    @functools.wraps(func)
    def deco(*args,**kwargs):
        print("--> benchmarking %s(%s,%s)" % (func.__name__,args,kwargs))
        ret = func(*args,**kwargs)
        print("<-- done")
    return deco

      

The main program can enable and disable individual decorators during import:

from toggledeco import benchmark, disable, enable

disable(benchmark) # no benchmarks...
import foopkg

enable(benchmark) # until they are enabled again
import barpkg

foopkg.foo() # no benchmarking 
barpkg.bar() # yes benchmarking

reload(foopkg)
foopkg.foo() # now with benchmarking

      

Output:

function in foopkg
--> benchmarking bar((),{})
function in barpkg
<-- done
--> benchmarking foo((),{})
function in foopkg
<-- done

      

It has an added bug / feature that enables / disables trickle down to any submodules imported from modules imported into the main function.

EDIT

Here's the class as suggested by @nneonneo. To use it, the decorator must be called as a function ( @benchmark()

, not @benchmark

).

class benchmark:
    disabled = False

    @classmethod
    def enable(cls):
        cls.disabled = False

    @classmethod
    def disable(cls):
        cls.disabled = True

    def __call__(cls,func):
        if cls.disabled:
            return func
        @functools.wraps(func)
        def deco(*args,**kwargs):
            print("--> benchmarking %s(%s,%s)" % (func.__name__,args,kwargs))
            ret = func(*args,**kwargs)
            print("<-- done")
        return deco

      

0


source


I would perform validation of the config file inside the decorator body. If the control parameter is to be used according to the config file, then I would go to your current decorator body. If not, I will return the function and do nothing. Something about this scent:

# deco.py
def benchmark(func):
  if config == 'dontUseDecorators': # no use of decorator
      # do nothing
      return func
  def decorator(): # else call decorator
      # fancy benchmarking 
  return decorator

      

What happens when a decorated function is called? @

in

@benchmark
def f():
    # body comes here

      

is syntactic sugar for this

f = benchmark(f)

      

so if config wants you not to see the decorator, you just do f = f()

what you expect.

0


source


I don't think anyone else has suggested this:

benchmark_modules = set('mod1', 'mod2') # Load this from a config file

def benchmark(func):
  if not func.__module__ in benchmark_modules:
      return func

  def decorator():
    # fancy benchmarking 
  return decorator

      

Every function or method has an attribute __module__

, which is the name of the module in which the function is defined. Create a whitelist (or blacklist, if you like) of the modules that you want to benchmark, and if you don't want to compare this module, just return the original undecorated function.

0


source


I think you should use decorator a to decorate decorator b, which allows decorator b to be turned on or off using a decision function.

It sounds complicated, but the idea is pretty simple.

So, let's say you have a decorator log:

from functools import wraps 
def logger(f):
    @wraps(f)
    def innerdecorator(*args, **kwargs):
        print (args, kwargs)
        res = f(*args, **kwargs)
        print res
        return res
    return innerdecorator

      

This is a very boring decorator, and I have a dozen or so of them, caches, lumberjacks, stuff injecting stuff, benchmarking, etc. I could easily extend it with an if statement, but that seems like a bad choice; because then I have to change a dozen decorators, which is not funny at all.

So what to do? Let it step one level higher. Let's say we have a decorator that can beautify the decorator? This decorator will look like this:

@point_cut_decorator(logger)
def my_oddly_behaving_function

      

This decorator takes a logger, which isn't very interesting. But it also has enough power to choose whether to use the logger or not use my_oddly_behaving_function. I named it point_cut_decorator because it has some aspect-oriented programming aspects. A point slice is a collection of places where some code (advice) should be intertwined with the flow of execution. Point section definitions are usually in one place. This method seems to be very similar.

How can we implement this solution logic. I chose a function that accepts a decorator, decorator, file, and name , which can only tell if a decorator should be applied or not. These are coordinates that are good enough to pinpoint a location accurately.

This is the implementation of point_cut_decorator, I decided to implement the decision function as a simple function, you can extend it to allow it to choose from your settings or config, if you use regexes for all 4 coordinates, you end up with something very powerful:

from functools import wraps

      

myselector is a decision function, on true the decorator is applied to false, it is not applied. The parameters are the filename, the module name, the decorated object, and finally the decorator. This allows us to switch behavior in a fine-grained manner.

def myselector(fname, name, decoratee, decorator):
    print fname

    if decoratee.__name__ == "test" and fname == "decorated.py" and decorator.__name__ == "logger":
        return True
    return False 

      

This decorates the function, checks myselector, and if myselector says further, it will apply the decorator to the function.

def point_cut_decorator(d):
    def innerdecorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if myselector(__file__, __name__, f, d):
                ps = d(f)
                return ps(*args, **kwargs)
            else:
                return f(*args, **kwargs)
        return wrapper
    return innerdecorator


def logger(f):
    @wraps(f)
    def innerdecorator(*args, **kwargs):
        print (args, kwargs)
        res = f(*args, **kwargs)
        print res
        return res
    return innerdecorator

      

And this is how you use it:

@point_cut_decorator(logger)
def test(a):
    print "hello"
    return "world"

test(1)

      

EDIT:

This is the regex approach I talked about:

from functools import wraps
import re

      

As you can see, I can specify a few rules somewhere that decide whether a decorator should be applied or not:

rules = [{
    "file": "decorated.py",
    "module": ".*",
    "decoratee": ".*test.*",
    "decorator": "logger"
}]

      

Then I iterate over all the rules and return True if the rule matches or false if the rule doesn't match. By creating rules in production, this won't slow down your application too much:

def myselector(fname, name, decoratee, decorator):
    for rule in rules:
        file_rule, module_rule, decoratee_rule, decorator_rule = rule["file"], rule["module"], rule["decoratee"], rule["decorator"]
        if (
            re.match(file_rule, fname)
            and re.match(module_rule, name)
            and re.match(decoratee_rule, decoratee.__name__)
            and re.match(decorator_rule, decorator.__name__)
        ):
            return True
    return False

      

0


source







All Articles