12

打开和关闭装饰器的最佳方法是什么,而不是实际去每个装饰并评论它?假设你有一个基准装饰器:

# 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

现在基准测试是在每个文件的基础上切换的,只需更改相关模块中的导入语句。单个装饰器可以独立控制。

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

4

7 回答 7

9

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

def benchmark(func):
    if not <config.use_benchmark>:
        return func
    def decorator():
    # fancy benchmarking 
    return decorator
于 2013-01-31T22:33:49.403 回答
4

我一直在使用以下方法。它与 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。

于 2015-02-22T04:25:31.683 回答
1

我认为你应该使用装饰器 a 来装饰装饰器 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_behave_function。我称它为 point_cut_decorator,因为它具有面向方面编程的某些方面。切入点是一组位置,其中一些代码(建议)必须与执行流程交织在一起。切点的定义通常在一个地方。这种技术似乎非常相似。

我们如何实现它的决策逻辑。好吧,我选择创建一个函数,它接受被装饰者、装饰者、文件名称,它只能说明是否应该应用装饰器。这些是坐标,足以非常精确地确定位置。

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

from functools import wraps

myselector 是决策函数,在 true 上应用装饰器,在 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
于 2013-10-27T20:47:12.687 回答
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")

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

切换deco.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(), 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
于 2013-03-08T19:55:41.590 回答
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__属性,该属性是定义函数的模块的名称。创建要进行基准测试的模块的白名单(或黑名单,如果您愿意),如果您不想对该模块进行基准测试,只需返回原始的未修饰函数。

于 2013-09-30T09:23:10.420 回答
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)

所以如果 config 想让你忽略装饰器,你只是在做f = f()你所期望的。

于 2013-09-30T08:35:33.027 回答
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)
于 2020-01-29T19:15:35.970 回答