我从这个stack_overflow_entry中了解到,Python装饰器按照它们在源中出现的顺序应用。
那么下面的代码片段应该如何表现?
@unittest.skip("Something no longer supported")
@skipIf(not something_feature_enabled, "Requires extra crunchy cookies to run")
def test_this():
....
第一个装饰者(如下所述)要求测试跑者完全跳过test_this()
@unittest.skip("Something no longer supported")
第二个装饰者要求测试运行员有条件地跳过test_this()
。
@skipIf(not something_feature_enabled, "Requires extra crunchy cookies to run")
这是否意味着test_this
根本不会被运行,除非我们先放置条件跳过装饰器?
另外,Python中有没有办法定义装饰器的依赖执行? e.g。
@skipIf("Something goes wrong")
@skipIf(not something_feature_enabled, "Requires extra crunchy cookies to run")
@log
@send_email
def test_this():
....
如果@log
为@send_email
,我们的想法是启用@skipIf("Something goes wrong")
和true
的执行。
道歉,如果我遗漏了一些非常明显的东西。
答案 0 :(得分:2)
我认为你可能错过了一个关键点:装饰器只是一个传递函数并返回函数的函数。
所以,这些是相同的:
@log
def test_this():
pass
def test_this():
pass
test_this = log(test_this)
同样地:
@skip("blah")
def test_this():
pass
def test_this():
pass
test_this = skip("blah")(test_this)
一旦你理解了这一点,你的所有问题都变得非常简单。
首先,是的,skip(…)
用于装饰skipIf(…)(test)
,所以如果它跳过装饰的东西,test
将永远不会被调用。
定义调用者调用顺序的方法是按照你想要的顺序编写它们。
如果你想动态地这样做,你可以通过动态应用装饰器来实现。例如:
for deco in sorted_list_of_decorators:
test = deco(test)
另外,Python中有没有办法定义装饰器的依赖执行?
不,他们都被执行了。与您所询问的内容更相关,每个装饰器都应用于装饰函数,而不是装饰器。
但是你总是可以将装饰器传递给条件装饰器:
def decorate_if(cond, deco):
return deco if cond else lambda f: f
然后:
@skipIf("Something goes wrong")
@decorate_if(something_feature_enabled, log)
@decorate_if(something_feature_enabled, send_email)
def test_this():
pass
简单,对吧?
现在,如果log
是真实的,则会应用send_email
和something_feature_enabled
装饰器;
但是如果你不能通过装饰器怎么办,因为功能已经装饰好了?好吧,如果你定义每个装饰器来展示它包裹的功能,你总是可以打开它。如果你总是使用functools.wraps
(如果没有其他理由你通常会这样做 - 而且即使你有这样的理由你也可以用这种方式轻松模仿),包裹的函数总是以{{{ 1}}。因此,您可以编写一个装饰器,有条件地删除最外层的装饰:
__wrapped__
同样,如果你想动态地做这件事,你可能会动态装饰。因此,一个更简单的解决方案就是跳过你不想要的装饰器,在它们被应用之前从def undecorate_if(cond):
def decorate(f):
return f.__unwrapped__ if cond else f
return decorate
迭代中移除它们。