在pythoncli中更好地使用“make_pass_decorator”

2024-04-19 18:58:40 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在寻找一些建议,以避免一个类两次实例化,这更像是一个设计模式问题。我正在使用Python Click库创建一个应用程序。在

我有一个Settings类,它首先将所有初始默认设置加载到字典中(硬编码到应用程序中),然后从用户计算机上的TOML文件将所有设置覆盖(如果指定的话)加载到字典中,然后最后将两者合并并使它们作为类实例(settings.<something>)的属性使用。在

对于大多数设置,我也希望能够指定命令行标志。然后,优先事项变为:

  1. 命令行标志。如果未指定,则回退到。。。在
  2. TOML文件中的用户设置。如果未指定,则最后回退到。。。在
  3. 应用程序默认值

为了达到这个结果,我发现,在使用Click的decorators时,我必须做如下操作:

^{1}$

为什么两次?

  • 需要settings = Settings()行来为@click.option修饰符提供default值。default值可以来自用户覆盖TOML文件(如果存在),也可以来自应用程序默认值。在
  • click.make_pass_decorator似乎是交错命令的推荐方式;甚至还提到了in their documentation。在函数内部,除了传递的CLI参数外,我有时还需要引用Settings类中的其他属性。在

我的问题是,哪个更好?有没有办法在其他click.option装饰器中使用pass_settings装饰器?还是应该完全放弃使用click.make_pass_decorator?在


Tags: 文件实例命令行用户应用程序字典属性settings
1条回答
网友
1楼 · 发布于 2024-04-19 18:58:40

解决不想实例化Settings两次的问题的一种方法是从click.Option继承,并将设置实例插入到 上下文直接类似于:

自定义类:

def build_settings_option_class(settings_instance):

    def set_default(default_name):

        class Cls(click.Option):
            def __init__(self, *args, **kwargs):
                kwargs['default'] = getattr(settings_instance, default_name)
                super(Cls, self).__init__(*args, **kwargs)

            def handle_parse_result(self, ctx, opts, args):
                obj = ctx.find_object(type(settings_instance))
                if obj is None:
                    ctx.obj = settings_instance

                return super(Cls, self).handle_parse_result(ctx, opts, args)

        return Cls

    return set_default

使用自定义类:

要使用自定义类,请将cls参数传递给@click.option()修饰符,如下所示:

^{pr2}$

这是怎么回事?

这是因为click是一个设计良好的OO框架。@click.option()修饰符通常实例化一个 click.Option对象,但允许使用cls参数覆盖此行为。所以相对来说 很容易在我们自己的类中从click.Option继承并超越所需的方法。在

在本例中,我们使用两个闭包来捕获设置实例和参数名。在返回的 类我们覆盖click.Option.handle_parse_result()以允许我们在上下文中插入设置对象。 这允许pass_settings装饰器在上下文中查找设置,因此不需要创建新实例。在

测试代码:

import click

class Settings(object):

    def __init__(self):
        self.instance_disk_size = 100
        self.instance_disk_type = 'pd-ssd'


settings = Settings()
settings_option_cls = build_settings_option_class(settings)
pass_settings = click.make_pass_decorator(Settings)


@click.command()
@click.help_option('-h', ' help')
@click.option(
    '-s', ' disk-size',
    cls=settings_option_cls('instance_disk_size'),
    help="Disk size",
    show_default=True,
    type=int
)
@click.option(
    '-t', ' disk-type',
    cls=settings_option_cls('instance_disk_type'),
    help="Disk type",
    show_default=True,
    type=click.Choice(['pd-standard', 'pd-ssd'])
)
@pass_settings
def create(settings, disk_size, disk_type):
    print(disk_size)
    print(disk_type)


if __name__ == "__main__":
    commands = (
        '-t pd-standard -s 200',
        '-t pd-standard',
        '-s 200',
        '',
        ' 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)
            create(cmd.split())

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

测试结果:

Click Version: 6.7
Python Version: 3.6.2 (default, Jul 17 2017, 23:14:31) 
[GCC 5.4.0 20160609]
     -
> -t pd-standard -s 200
200
pd-standard
     -
> -t pd-standard
100
pd-standard
     -
> -s 200
200
pd-ssd
     -
> 
100
pd-ssd
     -
>  help
Usage: test.py [OPTIONS]

Options:
  -h,  help                      Show this message and exit.
  -s,  disk-size INTEGER         Disk size  [default: 100]
  -t,  disk-type [pd-standard|pd-ssd]
                                  Disk type  [default: pd-ssd]

相关问题 更多 >