【问题标题】:Can python decorators be executed or skipped depending on an earlier decorator result?是否可以根据早期的装饰器结果执行或跳过 python 装饰器?
【发布时间】:2015-01-01 00:53:13
【问题描述】:

我从stack_overflow_entry 了解到,在 Python 中,装饰器是按照它们在源代码中出现的顺序应用的。

那么下面的代码 sn-p 应该如何表现呢?

@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 中有没有办法定义装饰器的依赖执行?例如

@skipIf("Something goes wrong")
@skipIf(not something_feature_enabled, "Requires extra crunchy cookies to run")
@log
@send_email
def test_this():
  ....

这个想法是如果@skipIf("Something goes wrong")true,则启用@log@send_email 的执行。

抱歉,如果我遗漏了一些非常明显的内容。

【问题讨论】:

    标签: python dependencies decorator execution python-decorators


    【解决方案1】:

    我认为你可能遗漏了一个关键点:装饰器只是一个传递函数并返回函数的函数。

    所以,它们是相同的:

    @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
    

    简单吧?

    现在,如果something_feature_enabled 为真,则将应用logsend_email 装饰器;否则,将应用不以任何方式装饰函数并仅将其原样返回的装饰器。

    但是如果你不能通过装饰器,因为函数已经被装饰了怎么办?好吧,如果你定义每个装饰器来暴露它被包装的函数,你总是可以解开它。如果你总是使用functools.wraps(如果你没有理由不这样做,你通常应该这样做——即使你有这样的理由,你也可以很容易地以这种方式进行模拟),包装的函数总是可以作为__wrapped__使用。因此,您可以编写一个有条件地删除最外层装饰的装饰器:

    def undecorate_if(cond):
        def decorate(f):
            return f.__unwrapped__ if cond else f
        return decorate
    

    再一次,如果您尝试动态地执行此操作,您可能会动态地进行装饰。因此,一个更简单的解决方案是跳过你不想要的装饰器,在它们被应用之前将它们从 decos 可迭代中删除。

    【讨论】:

    • 感谢@abarnert 提供如此详细的说明。
    猜你喜欢
    • 2012-01-20
    • 2017-02-04
    • 2012-03-30
    • 1970-01-01
    • 2023-03-04
    • 2018-11-18
    • 2016-07-25
    • 2018-03-10
    相关资源
    最近更新 更多