首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用自定义装饰器将多个公共选项放入一个参数的命令

使用自定义装饰器将多个公共选项放入一个参数的命令
EN

Stack Overflow用户
提问于 2019-05-17 11:53:31
回答 3查看 1K关注 0票数 9

我想要做一个模块,使它非常简单的构建点击命令,共享许多选项。这些选项将被提取为传递到命令中的单个对象。作为一个例子:

代码语言:javascript
复制
from magic import magic_command
import click

@magic_command('Colored')
@click.option('--color')
def cmd(magic, color):
    pass

然后,总计命令将有许多--magic-...选项,这些选项进入传递到cmdmagic对象。我做到了以下几点:

代码语言:javascript
复制
def magic_command(name):
    def decorator(func):
        @click.option('--magic-foo')
        @click.option('--magic-bar')
        def wrapper(magic_foo, magic_bar, **kwargs):
            print(f'initializing Magic with {magic_foo} and {magic_bar}')
            magic = Magic(magic_foo, magic_bar)
            func(magic, **kwargs)

        try:
            wrapper.__click_params__.extend(func.__click_params__)
        except AttributeError:
            pass

        return click.command(f'{name}-Magic')(wrapper)
    return decorator

然而,与__click_params__打交道似乎并不特别干净。

这个问题有点类似于this one,但是这种方法不允许我将许多神奇的选项浓缩成一个神奇的对象。

为了详细说明,用这种方法我将不得不做

代码语言:javascript
复制
@magic_command('Colored')
@click.option('--color')
def cmd(magic_foo, magic_bar, color):
    magic = Magic(magic_foo, magic_bar)
    pass

但这意味着定制代码需要知道有哪些神奇选项,以及如何构造魔术。我想这可以使用**kwargs进行简化,但理想情况下,我希望只将一个现成的magic对象传递给cmd

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-05-26 21:51:48

只需构造一个装饰器,就可以将多个选项提取为单个对象,如下所示:

代码:

代码语言:javascript
复制
def magic_options(func):
    @click.option('--magic-bar')
    @click.option('--magic-foo')
    def distill_magic(magic_foo, magic_bar, **kwargs):
        kwargs['magic'] = Magic(magic_foo, magic_bar)
        func(**kwargs)

    return distill_magic

用装饰器

然后,可以将装饰器应用于命令函数,如下所示:

代码语言:javascript
复制
@click.command('Colored-Magic')
@click.option('--color')
@magic_options
def cli(magic, color):
    ...

它需要应用到裸函数中。这是因为click.option返回的函数已经被click框架修改了,它的工作方式与您预期的不一样。

测试代码:

代码语言:javascript
复制
import click

@click.command('Colored-Magic')
@click.option('--color')
@magic_options
def cli(magic, color):
    click.echo(str(magic))
    click.echo(color)


class Magic(object):
    def __init__(self, magic_foo, magic_bar):
        self.magic_foo = magic_foo
        self.magic_bar = magic_bar

    def __str__(self):
        return "foo: {}  bar: {}".format(self.magic_foo, self.magic_bar)


if __name__ == "__main__":
    commands = (
        '--magic-foo fooby --magic-bar barbecue',
        '--magic-foo fooby',
        '--magic-bar barbecue',
        '',
        '--help',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            cli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

结果:

代码语言:javascript
复制
Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> --magic-foo fooby --magic-bar barbecue
foo: fooby  bar: barbecue

-----------
> --magic-foo fooby
foo: fooby  bar: None

-----------
> --magic-bar barbecue
foo: None  bar: barbecue

-----------
> 
foo: None  bar: None

-----------
> --help
Usage: test.py [OPTIONS]

Options:
  --color TEXT
  --magic-bar TEXT
  --magic-foo TEXT
  --help            Show this message and exit.
票数 6
EN

Stack Overflow用户

发布于 2019-05-25 06:04:04

在装饰器中更改函数的“魔力”是完全正常的:functools.wraps就是这样做的。所以你可以:

  1. 使用@wraps
  2. 在“@click.option”之后使用@wraps
  3. 将神奇选项定义为列表,并使用列表中的值解析args/kwargs。
代码语言:javascript
复制
from functools import wraps, WRAPPER_ASSIGNMENTS

DEFAULT_MAGIC_OPTIONS = ('--magic-foo', '--magic-bar')


def magic_command(name, magic_options=DEFAULT_MAGIC_OPTIONS):
    magic_options = magic_options or []
    magic_kwarg_names = [opt.split('--', 1)[1].replace('-', '_') for opt in magic_options]

    def decorator(func):
        @wraps(func, assigned=WRAPPER_ASSIGNMENTS+('__click_params__', ))
        def wrapper(*args, **kwargs):
            num_used_magic_args = min(len(magic_kwarg_names), len(args))

            magic_args = args[:num_used_magic_args]

            # If you want magic options to be "args only", then:
            # * you can raise TypeError if num_used_magic_args != len(magic_kwarg_names)
            # * you should not calculate `magic_kwargs`
            magic_kwargs = {}
            for kwarg_name in magic_kwarg_names[num_used_magic_args:]:
                if kwarg_name in kwargs:
                    magic_kwargs[kwarg_name] = kwargs.pop(kwarg_name)

            print(f'Initializing Magic with args={magic_args}, kwargs={magic_kwargs}')
            magic = Magic(*magic_args, **magic_kwargs)
            return func(magic, *args[num_used_magic_args:], **kwargs)

        for magic_option in magic_options[::-1]:  # Reverse order, to have proper positional arguments
            wrapper = click.option(magic_option)(wrapper)

        return click.command(f'{name}-Magic')(wrapper)

    return decorator

用法:

代码语言:javascript
复制
@magic_command('Colored')
@click.option('--color')  # Note: wrapper will be properly updated 
# with this @click.option, but related argument will not be passed
# into `Magic(...)` initialization.
# If you want `color` to be passed into `Magic`: specify it as one 
# of the items in `magic_options` argument of magic_command decorator:
# `@magic_command('Colored', magic_options=DEFAULT_MAGIC_OPTIONS+('color', ))`
# AND remove it from function definition here (keep only `magic`)
def cmd(magic, color):
    assert isinstance(magic, Magic)
    pass
票数 2
EN

Stack Overflow用户

发布于 2019-05-25 08:15:09

我不知道在不使用点击内部工具的情况下是否可以做你想做的事情,但是,肯定有办法,对吧?

无论如何,这里有一个使用另一个装饰师的解决方案。这个函数就放在这个函数的上面,它的功能是对magic_*参数进行分组。

代码语言:javascript
复制
def magic_command(f):
    f = click.option('--magic-bar')(f)
    f = click.option('--magic-foo')(f)
    f = click.command()(f)
    return f

def group_magic_args(f):
    def new_f(magic_foo, magic_bar, *args, **kwargs):
        magic = Magic(magic_foo, magic_bar)
        f(magic=magic, *args, **kwargs)
    return new_f

你会像这样使用新的装潢师:

代码语言:javascript
复制
@magic_command
@click.option('--color')
@group_magic_args
def cmd(magic, color):
    pass
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56185880

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档