click--命令行神器

一, 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容