美文网首页
click--命令行神器

click--命令行神器

作者: 大碗喝茶丶 | 来源:发表于2018-02-25 17:44 被阅读0次

    一, About Click

    如果想在启动python脚本的时候传入参数,我们通常会用sys.argv来获取参数列表,然后判断一下参数个数、类型等是否合法,这样当然可以,但如果用click的话可以很简单优雅的实现这些逻辑,并且它还支持更高端用法。

    本文是读了官方文档后做的记录,方便日后使用时查阅。

    下面是一个官方的例子:

    import click
    
    @click.command()
    @click.option('--count', default=1, help='Number of greetings.')
    @click.option('--name', prompt='Your name',
                  help='The person to greet.')
    def hello(count, name):
        """Simple program that greets NAME for a total of COUNT times."""
        for x in range(count):
            click.echo('Hello %s!' % name)
    
    if __name__ == '__main__':
        hello()
    

    加参数执行结果:

    $ python hello.py --count=3
    Your name: John
    Hello John!
    Hello John!
    Hello John!
    

    并且自动生成格式优美的帮助提示:

    $ python hello.py --help
    Usage: hello.py [OPTIONS]
    
    Simple program that greets NAME for a total of COUNT times.
    
    Options:
      --count INTEGER  Number of greetings.
      --name TEXT      The person to greet.
      --help           Show this message and exit.
    

    二, 安装

    pip install click
    

    三, 参数

    1,options与arguments

    click支持两种参数,option和 argument。两个有一些区别。
    以下特性只有options支持:

    • 未输入参数自动提示
    • 作为标记(boolean or otherwise)
    • options的值可以从环境变量中获取,而arguments不能。
    • options在帮助页面有详细文档,而arguments没有

    另外:arguments可以接收任意数量的参数,options只能接收指定数量的参数,默认是1.

    2,参数类型

    str / click.STRING
    int / click.INT
    float / click.FLOAT
    bool / click.BOOL
    ckick.UUID
    click.File
    click.Path
    click.Choice
    click.IntRange
    

    3, 参数名

    传递给函数的参数优先使用长名,即以--开头的名字,如果没有则使用以-开头的名。
    但如果有不含-的字符串,则直接用作变量名。
    如:
    ('-f', '--foo-bar') 传递给函数的参数名为foo_bar, ('-x',)则为x('-f', '--filename', 'dest')dest

    四,options

    1. 基本

    最基本的option是单值的,如果没有指定类型,那么则为string。option的默认值用default指定。

    @click.command()
    @click.option('--n', default=1)
    def dots(n):
        click.echo('.' * n)
    

    执行$ dots --n=2,输出..

    2. 多值选项

    当参数的值大于1个是,用参数nargs指定参数个数,option的参数个数是固定的。参数将以tuple的格式传递给变量。

    @click.command()
    @click.option('--pos', nargs=2, type=float)
    def findme(pos):
        click.echo('%s / %s' % pos)
    

    执行findme --pos 2.0 3.0,输出2.0 / 3.0

    3. 用tuple指定多个值的类型

    在上一个列子中两个参数的类型是相同的,但这可能并不是你想要的,有时候需要两个不同类型的值。那么可以这样。

    @click.command()
    @click.option('--item', type=(unicode, int))
    def putitem(item):
        click.echo('name=%s id=%d' % item)
    

    当type参数为tuple类型时,nargs为type的长度。
    执行putitem --item peter 1338,输出name=peter id=1338

    4. 多个相同选项

    类似但不同于多值选项,有时候需要多次输入相同的选项。

    @click.command()
    @click.option('--message', '-m', multiple=True)
    def commit(message):
        click.echo('\n'.join(message))
    

    执行commit -m foo -m bar
    输入:

     foo
     bar
    

    5. 计数

    @click.command()
    @click.option('-v', '--verbose', count=True)
    def log(verbose):
        click.echo('Verbosity: %s' % verbose)
    

    执行log -vvv,输出Verbosity: 3
    感觉这个功能有点鸡肋

    6. boolean标记

    布尔标记是用来启用或禁用的选项,你可以用/分隔符来实现启用或禁用选项。(当/在选项名中的时候,click就会认为它是个Boolean标记)。

    import sys
    
    @click.command()
    @click.option('--shout/--no-shout', default=False)
    def info(shout):
        rv = sys.platform
        if shout:
            rv = rv.upper() + '!!!!111'
        click.echo(rv)
    

    执行:

    $ info --shout
    LINUX2!!!!111
    $ info --no-shout
    linux2
    

    也可以不用/,而是用is_flag参数告知click这是一个boolean标记。

    @click.command()
    @click.option('--shout', is_flag=True)
    def info(shout):
        rv = sys.platform
        if shout:
            rv = rv.upper() + '!!!!111'
        click.echo(rv)
    

    boolean值默认是false.

    选项别名(如果某个不想定义,就写为空格):

    @click.command()
    @click.option('--shout/--no-shout', ' /-S', default=False)
    def info(shout):
        rv = sys.platform
        if shout:
            rv = rv.upper() + '!!!!111'
        click.echo(rv)
    

    执行:

    $ info --help
    Usage: info [OPTIONS]
    
    Options:
      --shout / -S, --no-shout
      --help
    

    7. Feature Switches

    我感觉Feature Switches有点类似于html表单里单选框,也可以用来实现类似Boolean标记的功能。
    多个option指定同一个名称,并设置flag_value

    import sys
    
    @click.command()
    @click.option('--upper', 'transformation', flag_value='upper',
              default=True)
    @click.option('--lower', 'transformation', flag_value='lower')
    def info(transformation):
        click.echo(getattr(sys.platform, transformation)())
    

    执行结果:

    $ info --upper
    LINUX2
    $ info --lower
    linux2
    $ info
    LINUX2
    

    8. 可选项

    @click.command()
    @click.option('--hash-type', type=click.Choice(['md5', 'sha1']))
    def digest(hash_type):
        click.echo(hash_type)
    

    执行:

    $ digest --hash-type=md5
    md5
    
    $ digest --hash-type=foo
    Usage: digest [OPTIONS]
    
    Error: Invalid value for "--hash-type": invalid choice: foo. (choose from md5, sha1)
    
    $ digest --help
    Usage: digest [OPTIONS]
    
    Options:
      --hash-type [md5|sha1]
      --help                  Show this message and exit.
    

    9. 提示

    如果option未输入时,提示。使用prompt参数,prompt=True则使用默认提示,也可以prompt="使用自定义提示语"

    @click.command()
    @click.option('--name', prompt='Your name please')
    def hello(name):
        click.echo('Hello %s!' % name)
    

    执行:

    $ hello
    Your name please: John
    Hello John!
    

    10. 密码

    隐藏输入字符,并两次输入确认。

    @click.command()
    @click.option('--password', prompt=True, hide_input=True,
              confirmation_prompt=True)
    def encrypt(password):
        click.echo('Encrypting password to %s' % password.encode('rot13'))
    

    执行:

    $ encrypt
    Password: 
    Repeat for confirmation: 
    Encrypting password to frperg
    

    更简单的:

    @click.command()
    @click.password_option()
    def encrypt(password):
        click.echo('Encrypting password to %s' % password.encode('rot13'))
    

    11. 动态默认值并提示

    设置auto_envvar_prefix和default_map参数后 可以从环境变量或配置文件中读取默认值,但是这更改了提示语机制,使用户不能交互式的输入。
    这么做可以两者兼得:

    @click.command()
    @click.option('--username', prompt=True,
              default=lambda: os.environ.get('USER', ''))
    def hello(username):
        print("Hello,", username)
    

    12. Callbacks and Eager Options

    todo

    13. YES

    有些操作需要提示用户确认后再执行。

    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!
    

    同样的:

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

    14. 从环境变量取值

    两种方式:
    1,调用时指定auto_envver_prefix参数,则自动使用以auto_envver_prefix为前缀、option名为后缀下划线分割的大写环境变量。

    @click.command()
    @click.option('--username')
    def greet(username):
        click.echo('Hello %s!' % username)
    
    if __name__ == '__main__':
        greet(auto_envvar_prefix='GREETER')
    

    And from the command line:

    $ export GREETER_USERNAME=john
    $ greet
    Hello john!
    

    2,手动指定以envvar参数指定环境变量

    @click.command()
    @click.option('--username', envvar='USERNAME')
    def greet(username):
        click.echo('Hello %s!' % username)
    
    if __name__ == '__main__':
        greet()
    

    And from the command line:

    $ export USERNAME=john
    $ greet
    Hello john!
    

    15. 多个值的环境变量

    option可以接收多个值的参数,从环境变量中获取多值有点复杂,Click把它交给type参数来解决,但multiplenargs的值大于1的时候,Click将调用 ParamType.split_envvar_value()来执行分割。除typeFilePath之外的其它类型将全部以空格分割。

    @click.command()
    @click.option('paths', '--path', envvar='PATHS', multiple=True,
                  type=click.Path())
    def perform(paths):
        for path in paths:
            click.echo(path)
    
    if __name__ == '__main__':
        perform()
    

    And from the command line:

    $ export PATHS=./foo/bar:./test
    $ perform
    ./foo/bar
    ./test
    

    16. 区间

    IntRange 有两种模式:
    1,默认模式,当超出范围后抛出异常
    2,clamp=True时,越界后取极值,比如当区间为0-5, 10则为5, -1为0

    @click.command()
    @click.option('--count', type=click.IntRange(0, 20, clamp=True))
    @click.option('--digit', type=click.IntRange(0, 10))
    def repeat(count, digit):
        click.echo(str(digit) * count)
    
    if __name__ == '__main__':
        repeat()
    

    And from the command line:

    $ repeat --count=1000 --digit=5
    55555555555555555555
    $ repeat --count=1000 --digit=12
    Usage: repeat [OPTIONS]
    Error: Invalid value for "--digit": 12 is not in the valid range of 0 to 10.
    

    当区间的一边设置为None时,表示不限制

    17. 自定义校验

    如果你需要自定义校验逻辑,可以通过callback参数实现。回调函数既可以改变值也可以抛出异常。

    def validate_rolls(ctx, param, value):
        try:
            rolls, dice = map(int, value.split('d', 2))
            return (dice, rolls)
        except ValueError:
            raise click.BadParameter('rolls need to be in format NdM')
    
    @click.command()
    @click.option('--rolls', callback=validate_rolls, default='1d6')
    def roll(rolls):
        click.echo('Rolling a %d-sided dice %d time(s)' % rolls)
    
    if __name__ == '__main__':
        roll()
    

    And what it looks like:

    $ roll --rolls=42
    Usage: roll [OPTIONS]
    
    Error: Invalid value for "--rolls": rolls need to be in format NdM
    
    $ roll --rolls=2d12
    Rolling a 12-sided dice 2 time(s)
    

    五. Arguments

    Arguments只支持Option特性的子集,Click不会为Arguments参数生成帮助文档。

    1. 基本用法

    默认类型是string.

    @click.command()
    @click.argument('filename')
    def touch(filename):
        click.echo(filename)
    

    And what it looks like:

    $ touch foo.txt
    foo.txt
    

    2. 变长参数

    用参数nargs指定值的格式,-1表示最大,但只能有一个参数被设置为-1,因为它会获取剩下所有的值。

    @click.command()
    @click.argument('src', nargs=-1)
    @click.argument('dst', nargs=1)
    def copy(src, dst):
        for fn in src:
            click.echo('move %s to folder %s' % (fn, dst))
    

    And what it looks like:

    $ copy foo.txt bar.txt my_folder
    move foo.txt to folder my_folder
    move bar.txt to folder my_folder
    

    3.文件参数

    Click通过click.File类型为您智能处理文件提供支持.

    @click.command()
    @click.argument('input', type=click.File('rb'))
    @click.argument('output', type=click.File('wb'))
    def inout(input, output):
        while True:
            chunk = input.read(1024)
            if not chunk:
                break
            output.write(chunk)
    

    And what it does:

    $ inout - hello.txt
    hello
    ^D
    $ inout hello.txt -
    hello
    

    -代表stdin/stdout

    4.文件路径参数

    @click.command()
    @click.argument('f', type=click.Path(exists=True))
    def touch(f):
        click.echo(click.format_filename(f))
    

    And what it does:

    $ touch hello.txt
    hello.txt
    
    $ touch missing.txt
    Usage: touch [OPTIONS] F
    
    Error: Invalid value for "f": Path "missing.txt" does not exist.
    

    5.环境变量

    像option一样argument也支持获取从环境变量中读取值,和option不同的是,它只支持明确指定环境变量名的方式。

    @click.command()
    @click.argument('src', envvar='SRC', type=click.File('r'))
    def echo(src):
        click.echo(src.read())
    

    And from the command line:

    $ export SRC=hello.txt
    $ echo
    Hello World!
    

    6. Option-Like Arguments

    假如有一个文件-foo.txt,如果把这个作为argument的值,click会把它当成一个option的值。为了解决这个问题,像其它POSIX格式的命令行那样,click把--当成option和argument的分割符。

    @click.command()
    @click.argument('files', nargs=-1, type=click.Path())
    def touch(files):
        for filename in files:
            click.echo(filename)
    

    And from the command line:

    $ touch -- -foo.txt bar.txt
    -foo.txt
    bar.txt
    

    六. Commands and Groups

    Click最重要的特征之一是任意嵌套的命令行,这个特性通过command和group(MultiCommand)实现。

    1. 回调

    对一个常规的command来说,只要command运行,回调必然执行,除非参数的回调函数打断了它,比如--help
    但对于group和多个command来说,情况不同。回调仅在子命令调用时执行。

    @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')
    

    Here is what this looks like:

    $ 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
    

    2. 传递参数

    Click验证区分命令和子命令的参数,这意味着option和argument必须跟在它对应的command之后,而在其他command之前。可以用--help来查看说明。比如一个程序tools.py, 有一个名叫sub的子命令。

    • tool.py --help 将返回整个程序的帮助
    • tool.py sub --help 将返回子命令的 帮助
    • tool.py --help subtool.py --help效果一样,因为--help会中断子命令继续执行。

    3. 嵌套处理和上下文

    在第一个例子中,debug参数并没有传递给子命令,子命令只能接收它自己的参数。如果嵌套命令想相互通信,可以通过Context.
    每当一个命令被调用时,一个上下文对象将产生,并且和上一个上下文对象连接。通常看不到它们,但确实存在,上下文对象会自动和参数的值一起传递给参数的回调函数。Command也可以通过使用pass_context()修饰器手动的请求传递上下文对象给它们。
    上下文对象也可以携带其它你附加的对象。

    @click.group()
    @click.option('--debug/--no-debug', default=False)
    @click.pass_context
    def cli(ctx, debug):
        ctx.obj['DEBUG'] = debug
        print id(ctx.obj)
        print id(ctx)
    
    @cli.command()
    @click.pass_context  
    def sync(ctx):
        click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
        print id(ctx.obj)
        print id(ctx)
        print id(ctx.parent)
    
    if __name__ == '__main__':
        cli(obj={})
    

    执行:

    python s.py --debug sync
    140673188343888
    140673188304592
    Debug is on
    140673188343888
    140673188304656
    140673188304592
    

    可以看出上下文的obj对象是共享的,但是会被重写,想访问上一级上下文,可以通过ctx.parent

    4. Decorating Commands

    pass

    5. Group Invocation Without Command

    默认情况下group和command不会被调用除非有子命令,事实上会没有子命令时会调用--help. 可以通过传参invoke_without_command=True来改变这逻辑。在下面的例子中不管有没有子命令回调都将执行,上下文对象中包含了是否调用子命令信息。

    @click.group(invoke_without_command=True)
    @click.pass_context
    def cli(ctx):
        if ctx.invoked_subcommand is None:
            click.echo('I was invoked without subcommand')
        else:
            click.echo('I am about to invoke %s' % ctx.invoked_subcommand)
    
    @cli.command()
    def sync():
        click.echo('The subcommand')
    

    And how it works in practice:

    $ tool
    I was invoked without subcommand  
    $ tool sync
    I am about to invoke sync
    The subcommand
    

    6. Custom Multi Commands

    pass

    7.Merging Multi Commands

    pass

    8. Multi Command Pipelines

    pass

    9. Overriding Defaults

    pass

    10.Context Defaults

    pass

    11. Command Return Values

    pass

    相关文章

      网友评论

          本文标题:click--命令行神器

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