打开和关闭装饰器的最佳方法是什么,而不是实际去每个装饰并将其评论出来?假设您有一个基准测试装饰器:
# 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语句。个别装饰者可以独立控制。
有更好的方法吗?最好不要编辑源文件,并指定在其他文件中使用哪些装饰器。
答案 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)