切换装饰器

时间:2013-01-31 22:31:17

标签: python decorator

打开和关闭装饰器的最佳方法是什么,而不是实际去每个装饰并将其评论出来?假设您有一个基准测试装饰器:

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

在您的模块中,例如:

# mymodule.py
from deco import benchmark

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

  @benchmark
  def g():
    # more code

这没关系,但有时你不关心基准测试,也不想要开销。我一直在做以下事情。添加另一个装饰者:

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

然后注释掉导入行并添加另一行:

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

现在基于每个文件切换基准测试,只需要更改相关模块中的import语句。个别装饰者可以独立控制。

有更好的方法吗?最好不要编辑源文件,并指定在其他文件中使用哪些装饰器。

7 个答案:

答案 0 :(得分:5)

您可以将条件添加到装饰器本身:

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

答案 1 :(得分:3)

我一直在使用以下方法。它与CaptainMurphy建议的几乎完全相同,但它的优势在于你不需要像装置一样调用装饰器。

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's 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")

在上面,example1将被装饰,而example2将不会被装饰。当我必须通过模块启用或禁用装饰器时,我只需要一个函数,只要我需要一个不同的副本就可以生成一个新的SwitchedDecorator。

答案 2 :(得分:0)

这是我最终为每个模块切换提出的。它使用@ nneonneo的建议作为起点。

随机模块使用装饰器正常,不知道切换。

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")

装饰器模块本身为所有已禁用的装饰器维护一组函数引用,每个装饰器检查它在该集合中是否存在。如果是这样,它只返回原始函数(没有装饰器)。默认情况下,该集为空(一切都已启用)。

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

主程序可以在导入期间打开和关闭各个装饰器:

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

输出:

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

这有添加的错误/功能,启用/禁用将逐渐渗透到从主函数中导入的模块导入的任何子模块。

修改

这是@nneonneo建议的课程。为了使用它,必须将装饰器作为函数调用(@benchmark(),而不是@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

答案 3 :(得分:0)

我会在装饰器的主体内部实现对配置文件的检查。如果必须根据配置文件使用基准测试,那么我会去你当前的装饰者的身体。如果没有,我会返回该功能,不做任何其他事情。这种味道的东西:

# 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

调用装饰函数会发生什么?

中的@
@benchmark
def f():
    # body comes here

是这个

的语法糖
f = benchmark(f)

所以,如果配置要求你忽视装饰,你只是在做f = f()这就是你所期望的。

答案 4 :(得分:0)

我认为还没有人建议这样做:

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

每个函数或方法都有一个__module__属性,该属性是定义函数的模块的名称。如果要进行基准测试,请创建一个白名单(如果您愿意,请列入黑名单),如果您不想对该模块进行基准测试,只需返回原始未修饰的功能。

答案 5 :(得分:0)

我认为你应该使用装饰器来装饰装饰器b,这可以让你在决策功能的帮助下打开或关闭装饰器b。

这听起来很复杂,但这个想法很简单。

所以假设你有一个装饰记录器:

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

这是一个非常无聊的装饰者,我有十几个这样的,牧师,伐木工,注入东西的东西,基准等等。我可以用if语句轻松扩展它,但这似乎是一个糟糕的选择;因为那时我必须更换十几个装饰器,这根本不好玩。

那该怎么办?让我们更高一级。假设我们有装饰器,可以装饰装饰器?这个装饰器看起来像这样:

@point_cut_decorator(logger)
def my_oddly_behaving_function

这个装饰者接受记录器,这不是一个非常有趣的事实。但它也有足够的力量来选择是否应该将记录器应用于my_oddly_behaving_function。我称之为point_cut_decorator,因为它具有面向方面编程的某些方面。切入点是一组位置,其中一些代码(建议)必须与执行流交织在一起。切点的定义通常在一个地方。这种技术似乎非常相似。

我们如何实施决策逻辑。好吧,我选择了一个函数,它接受decoratee,装饰器,文件名称,这只能说是否应该应用装饰器。这些是坐标,足以精确定位位置。

这是point_cut_decorator的实现,我选择将决策函数实现为一个简单的函数,你可以扩展它以让它根据你的设置或配置来决定,如果你对所有4个坐标使用正则表达式,你最终会有一些非常强大的东西:

from functools import wraps

myselector是决策函数,如果真的是装饰器应用于false则不应用它。参数是文件名,模块名称,装饰对象,最后是装饰器。这允许我们以细粒度的方式切换行为。

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

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

这会修饰一个函数,检查myselector,如果myselector说继续,它会将装饰器应用到函数中。

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

这就是你如何使用它:

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

test(1)

修改

这是我所说的正则表达式方法:

from functools import wraps
import re

正如您所看到的,我可以在某处指定一些规则,这些规则决定是否应该应用装饰器:

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

然后我遍历所有规则并在规则匹配时返回True,如果规则不匹配则返回false。通过在生产中将规则清空,这不会太慢减慢您的应用程序:

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

答案 6 :(得分:0)

另一种直接方法:

# mymodule.py
from deco import benchmark

class foo(object):

  def f():
    # code

  if <config.use_benchmark>:
    f = benchmark(f)

  def g():
    # more code

  if <config.use_benchmark>:
    g = benchmark(g)