【问题标题】:Decorator which conditionally activates another decorator?有条件地激活另一个装饰器的装饰器?
【发布时间】:2013-08-02 01:06:18
【问题描述】:

我有一些函数,在正常情况下,使用用户输入提供的参数调用这些函数。但是,使用某些系列参数调用其中一些函数是有效的,这些参数是在运行时根据某些系统状态确定的。

我希望用户能够可选地指示我的程序使用所有有效输入调用这些函数并返回每个调用的结果。我认为一个装饰器的工作类似于激活开关的功能,它具有另一个装饰器,指示使用哪个系列的参数会很好地工作。

此外,我需要保留函数签名和元数据。这对我的程序的运行至关重要。

这是我尝试过的,但它不起作用。它基于this example

>>> from decorator import decorator
>>> def the_list():
...     return ["foo", "bar", "baz"]
... 
>>> import itertools
>>> @decorator
... def do_all(func):
...     # this will do nothing (besides wrap in a tuple) unless func is decorated with @gets_arg_from
...     if hasattr(func, 'get_from'):
...         return tuple(func(*args) for args in itertools.product(*(list_fun() for list_fun in func.get_from)))
...     else:
...         return (func(),)
... 
>>> def gets_arg_from(*list_funcs):
...     # this will do nothing to func unless decorated with @do_all
...     def gets_arg_from(func, *args, **kwargs):
...         func.get_from = list_funcs
...         return func(*args, **kwargs)
...     return decorator(gets_arg_from)
... 
>>> @gets_arg_from(the_list)
... def print_func(word):
...     # this will print its argument unless decorated with @do_all
...     # at that point it will print every element returned by the_list()
...     print word
... 
>>> print_func("foo")
foo
>>> all = decorator(do_all, print_func)
>>> all()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_func() takes exactly 1 argument (0 given)
>>> print_func.get_from
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'get_from'

我的预期是:

>>> all()
("foo", "bar", "baz")

我注意到的是错误的:

  1. gets_arg_from 不会将 get_from 属性添加到 func
  2. 关于我使用符号@gets_arg_from(the_list) 是错误的。它认为我正在尝试传递两个参数(但为什么这会成为问题?)

至于我的动机,我想到了装饰器,因为实际上有数百个这样的例程,它们的实现细节(以及它们的功能正确性)经常变化,我不想使用 @987654329 @ 来根据他们的参数名称来推理要做什么,我也不想为每个有意义的函数硬编码 do_all 功能。类方法可能有效,但就我的目的而言,它们是语义设计的。此外,为了其他可能需要维护我的代码的人,我认为让他们应用装饰器而不用担心其余部分更容易,而不是使用某些参数名称或将函数放在某个类中或任何。我意识到这个问题可能听起来很奇怪,所以我想这个脚注可能会让我看起来不像个疯子。

【问题讨论】:

  • 你实际上在做什么?这听起来有点像单元测试。你在进行单元测试吗?
  • @user2357112 不,我不是。我正在尝试为具有某些属性(即特定类型的装饰器)的函数进行一种“切换”。
  • 我不确定我是否 100% 关注 - 但我最初的想法是你能在这里与 functools.partial 合作吗...?
  • @JonClements 以什么方式?根据decorator documentationgets_arg_fromdo_all 实际上是functools.partials。

标签: python python-2.7 decorator python-decorators


【解决方案1】:

这不是在做你想做的事吗?

import functools
from itertools import product

def the_list():
    return ["foo", "bar", "baz"]


def do_all(func):
    if hasattr(func, 'get_from'):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return tuple(func(*args) for args in
                         product(*(lf() for lf in func.get_from)))
        return wrapper
    return func

def gets_arg_from(*list_funcs):
    def decorator(func):
        func.get_from = list_funcs
        return func
    return decorator

@gets_arg_from(the_list)
def print_func(word):
    return word

print print_func('foo')

all = do_all(print_func)
print all()

编辑:解释

这两个代码段是相同的:

@deco
def func(...):
    some code

相同
func = deco(lambda ...: some code)

@something 只是函数调用和匿名函数创建的语法糖......

我会在接下来的代码和平中一步一步解释发生了什么:

@gets_arg_from(the_list)
def print_func(word):
     return word
  1. 首先 Python 创建一个匿名函数,它接收参数 word,并有一个只返回这个 word 的函数体(或执行函数体所做的任何事情)

  2. 然后函数get_arg_from被调用,the_list作为参数传递给它

  3. get_arg_from 创建一个decorator 函数并返回它

  4. 调用从get_arg_from 返回的decorator 函数(这是语法糖)作为参数传递func 在步骤1 中创建的匿名函数。

  5. decorator只是将list_funcs元组赋给匿名函数的get_from属性,并返回匿名函数

  6. decorator函数的返回值赋值给变量print_func

类似的效果可以通过以下方式实现:

def __anonimous(word):
    return word
__decorator = gets_arg_from(the_list)
print_func = __decorator(__anonimous)

所以基本上gets_arg_from 不是装饰器,它是一个返回装饰器的函数。

另一方面,

do_all 一个装饰器,它接收一个函数作为参数,并返回原始函数(如果函数没有属性get_from)或替换原始函数的wrapper 函数(如果它具有get_from 属性)。

您可以找到更多示例here

【讨论】:

  • 是的,看起来是这样。为什么使用 @gets_arg_from 装饰器时会保留签名?显然我知道的不够多,无法说出原因,但我希望得到一个解释,因为我认为装饰会立即杀死 arg 规范。
  • “签名得到保留”是什么意思?
  • inspect.getargspec(print_func) 给了我ArgSpec(args=['word'], varargs=None, keywords=None, defaults=None)。我希望它能给我来自 gets_arg_from 的 arg 规范……显然,我对装饰器在 Python 中的工作方式感到困惑。
  • 装饰器是接收一个函数作为参数并返回另一个替换原始函数的函数。由于我无法在评论中给出好的代码示例,我将创建另一个答案。
猜你喜欢
  • 2017-08-23
  • 2011-09-03
  • 2017-11-16
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 2015-02-22
  • 2018-06-21
  • 1970-01-01
相关资源
最近更新 更多