Click

作者: shrek911 | 来源:发表于2017-11-01 14:01 被阅读0次

    CLI是“命令行界面”
    @click.command() 装饰一个函数,就能使之成为命令行
    @click.option() 添加命令行选项
    @click.group 把函数A装饰为Group对象,就可以拥有多个子命令
    后面用 A.add_command(hello) 就把hello函数加入到A中了
    注意, hello 是被装饰成命令行的函数
    也可用 @A.group() 装饰函数 hello ,效果是一样的


    Basic Concepts #

    装饰器是一个函数,它接受另一个函数作为参数,并返回一个新函数。
    Click是基于装饰器的。装饰器将函数变成一个可调用的脚本(命令):
    A function becomes a Click command line tool by decorating it through click.command(). At its simplest, just decorating a function with this decorator will make it into a callable script:

    import click
    
    @click.command()
    def hello():
        click.echo('Hello World!')
    

    What’s happening is that the decorator converts the function into a Command
    which then can be invoked:

    if __name__ == '__main__':
        hello()
    

    And what it looks like:

    $ python hello.py
    Hello World!
    

    And the corresponding help page:

    $ python hello.py --help
    Usage: hello.py [OPTIONS]
    
    Options:
      --help  Show this message and exit.
    

    Options

    Callbacks and Eager Options #

    有时想用一个参数就改变执行流程。比如输入 --version 参数,就不执行脚本,而只是在屏幕上输出版本信息。

    这就出现了两个概念:eager参数callback,eager就是优先级(--version的要求比其他参数更急切),回调函数就是要执行的动作(输出版本信息)。

    callback 有两个参数:the current Context and the value. 前者(是Click提供的一个内部对象)提供一些功能,比如退出应用程序和 gives access to other already processed parameters.

    def print_version(ctx, param, value): # 这里的ctx就是Context
        if not value or ctx.resilient_parsing:
            return
        click.echo('Version 1.0')
        ctx.exit()
    
    @click.command()
    @click.option('--version', is_flag=True, callback=print_version,
                  expose_value=False, is_eager=True)
    def hello():
        click.echo('Hello World!')
    

    expose_value=False 会避免传递一个boolean给hello脚本,ctx.resilient_parsing 检查命令行是否改变执行流。
    The expose_value parameter prevents the pretty pointless(无谓的) version parameter from being passed to the callback. If that was not specified, a boolean would be passed to the hello script. The resilient_parsing flag is applied to the context if Click wants to parse the command line without any destructive(毁灭性) behavior that would change the execution flow. In this case, because we would exit the program, we instead do nothing.

    What it looks like:

    $ hello
    Hello World!
    $ hello --version
    Version 1.0
    

    如上所述,这里的 callback 是指 Click.option 的一个参数;
    下面这一段将介绍一般性的概念 callback ,与本文主题关系不大:

    通过指针来调用,就是回调函数。如果把函数A的指针p(地址)作为参数传递给另一个函数B,当指针被用来调用其函数A时,我们就说发生了回调(callback)。回调函数不是由该函数的实现方直接调用,而是在特定的事件发生时由另外的一方调用,用于对该事件进行响应。

    o_回调.JPG
    从应用的层面完全看不出来这和普通的调用(call)有什么区别,那为什么还要叫callback呢?我猜是因为实现原理与call有所不同,或者纯粹是系统(OS、编译器、解释器)给出的概念(参考文 callback.jpg

    Yes Parameters#

    对于一些危险的命令,我们需要让用户再次确认。这可以给命令加上一个 --yes flag 并在 callback 中向用户要求确认,否则就中止该命令脚本。
    ...and asking for confirmation if the user did not provide it and to fail in a callback:

    def abort_if_false(ctx, param, value):
        if not value:
            ctx.abort()
    
    @click.command()
    @click.option('--yes', is_flag=True, callback=abort_if_false,
                  expose_value=False,
                  prompt='Are you sure you want to drop the db?')
    def dropdb():
        click.echo('Dropped all tables!')
    

    效果如下:

    $ dropdb
    Are you sure you want to drop the db? [y/N]: n
    Aborted!
    $ dropdb --yes
    Dropped all tables!
    

    还可以使用 confirmation_option() :

    @click.command()
    @click.confirmation_option(prompt='Are you sure you want to drop the db?')
    def dropdb():
        click.echo('Dropped all tables!')
    

    Commands and Groups

    Click可以任意构造命令行程序,这是由...来实现的。
    The most important feature of Click is the concept of arbitrarily nesting command line utilities(实用程序). This is implemented through the Command and Group
    (actually MultiCommand).

    Callback Invocation 回调请求

    对于常规的命令,callback总是立即执行(除非某个参数阻止了它,比如--help)。
    For a regular command, the callback is executed whenever the command runs. If the script is the only command, it will always fire (unless a parameter callback prevents it. This for instance happens if someone passes --help to the script).

    但是,用group和multi commands时,只要子命令运行了,callback就会被触发。
    For groups and multi commands, the situation looks different. In this case, the callback fires whenever a subcommand fires (unless this behavior is changed). What this means in practice is that an outer command runs when an inner command runs:

    @click.group()
    @click.option('--debug/--no-debug', default=False)
    def cli(debug):
        click.echo('Debug mode is %s' % ('on' if debug else 'off'))
    
    @cli.command()
    def sync():
        click.echo('Synching')
    

    效果如下:

    $ tool.py
    Usage: tool.py [OPTIONS] COMMAND [ARGS]...
    
    Options:
      --debug / --no-debug
      --help                Show this message and exit.
    
    Commands:
      sync
    
    $ tool.py --debug sync
    Debug mode is on
    Synching
    

    Passing Parameters

    Click strictly separates parameters between commands and subcommands. What this means is that options and arguments for a specific command have to be specified after the command name itself, but before any other command names.

    This behavior is already observable with the predefined --help option. Suppose we have a program called tool.py, containing a subcommand called sub.

    • tool.py --help will return the help for the whole program (listing subcommands).
    • tool.py sub --help will return the help for the sub subcommand.
    • But tool.py --help sub will treat --help as an argument for the main program. Click then invokes the callback for --help, which prints the help and aborts the program before click can process the subcommand.

    Nested Handling and Contexts #

    前面的例子 $ tool.py --debug sync 中,group(即 tool )接受一个debug参数传给了它的callback,而 sync 则需接受自己的参数。
    As you can see from the earlier example, the basic command group accepts a debug argument which is passed to its callback, but not to the sync command itself. The sync command only accepts its own arguments.

    This allows tools to act completely independent of each other, but how does one command talk to a nested one? The answer to this is the Context.

    每次一个命令被唤起时,就会产生一个新的 context对象(上下文环境)。这些 context对象 会随值一起、自动地传给 callback参数,但命令也能主动请求 context对象,只需用一个 pass_context() 装饰器即可——此时,context 是被装饰函数的第一个参数。
    Each time a command is invoked, a new context is created and linked with the parent context. Normally, you can’t see these contexts, but they are there. Contexts are passed to parameter callbacks together with the value automatically. Commands can also ask for the context to be passed by marking themselves with the pass_context() decorator. In that case, the context is passed as first argument.

    此 context对象 还能携带一个你指定的对象,比如:
    The context can also carry a program specified object that can be used for the program’s purposes. What this means is that you can build a script like this:

    @click.group()
    @click.option('--debug/--no-debug', default=False)
    @click.pass_context
    def cli(ctx, debug):
        ctx.obj['DEBUG'] = debug # obj – an arbitrary object of user data.
    
    @cli.command()
    @click.pass_context
    def sync(ctx):
        click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
    
    if __name__ == '__main__':
        cli(obj={})
    

    一级级的向下传递、覆盖,
    If the object is provided, each context will pass the object onwards(向前) to its children, but at any level a context’s object can be overridden. To reach to a parent, context.parent can be used.

    除此以外,没有什么能阻止应用程序修改全局状态。???
    In addition to that, instead of passing an object down, nothing stops the application from modifying global state. For instance, you could just flip(快速翻动) a global DEBUG variable and be done with it.

    Decorating Commands #

    事实上,callback 是由 Context.invoke() 来调用的。
    As you have seen in the earlier example, a decorator can change how a command is invoked. What actually happens behind the scenes is that callbacks are always invoked through the Context.invoke() method which automatically invokes a command correctly (by either passing the context or not).

    直接看示例吧
    This is very useful when you want to write custom decorators. For instance, a common pattern would be to configure an object representing state and then storing it on the context and then to use a custom decorator to find the most recent object of this sort and pass it as first argument.

    from functools import update_wrapper
    
    def pass_obj(f):
        @click.pass_context
        def new_func(ctx, *args, **kwargs):
            return ctx.invoke(f, ctx.obj, *args, **kwargs)
        return update_wrapper(new_func, f)
    

    The Context.invoke() command will automatically invoke the function in the correct way, so the function will either be called with f(ctx, obj) or f(obj) depending on whether or not it itself is decorated with pass_context().

    This is a very powerful concept that can be used to build very complex nested applications; see Complex Applications for more information.

    相关文章

      网友评论

          本文标题:Click

          本文链接:https://www.haomeiwen.com/subject/fmtgpxtx.html