在 Pyinvoke @task 装饰器中使用多个装饰器

Using multiple decorators with Pyinvoke @task decorator

我正在尝试创建装饰器,并将其与 pyinvoke @task 装饰器一起使用。

参见:

def extract_config(func):
    print func

    def func_wrapper(cfg=None):
        config = read_invoke_config(cfg)
        return func(**config)

    return func_wrapper


@extract_config
@task
def zip_files(config):
    import zipfile
    ...

但是,现在从命令行执行

inv zip_files

我收到输出:

<Task 'zip_files'> No idea what 'zip_files' is!

不管@task是在@extract_config之前还是之后,我都输了 调用任务功能,它无法识别函数名称..

我做错了什么?

语法:

@deco1
@deco2
def func(): pass

几乎等同于:

def func(): pass
func = deco1(deco2(func))

所以第一行的装饰器在第二行的装饰器之后应用。

您正在使用的 task 装饰器来自您的库 returns 一个 Task 对象而不是另一个函数,因此您的外部装饰器在替换它时可能没有做正确的事情具有包装函数的全局命名空间。为了让它工作,你需要你的 wrapper 成为一个模仿 Task 对象的所有相关行为的对象(我不知道这可能有多容易或多难)。

更直接的方法可能是颠倒装饰器的顺序:

@task
@extract_config
def zip_files(config):

这可能更接近工作,但我怀疑它仍然出错,原因很简单。您的 extract_config 装饰器 returns 一个与 zip_files 名称不同的函数(它 returns 名为 wrapper 的函数,它没有做出任何努力来改变它__name__ 属性),所以 Task 对象不知道它的正确名称。

为了解决这个问题,我建议在装饰器中使用 functools.wraps 将包装函数的相关属性复制到包装函数中:

def extract_config(func):
    print func

    @functools.wraps(func)
    def func_wrapper(cfg=None):
        config = read_invoke_config(cfg)
        return func(**config)

    return func_wrapper

我无法使用 Blcknght 的解决方案,但我找到了解决方法:

def decorator(fn):
    @functools.wraps(fn)
    def _decorated(ctx, *args, **kwargs):
        return fn(ctx, *args, **kwargs)
    return _decorated

@invoke.task
def some_task(ctx, config="Something"):
    @decorator
    def _inner(ctx):
        print(config)
        ...

    return _inner(ctx)

包装内部函数可以防止它掩盖底层函数,允许调用识别装饰器并允许它检查函数的参数。