一, 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
参数来解决,但multiple
或nargs
的值大于1的时候,Click将调用 ParamType.split_envvar_value()
来执行分割。除type
为File
和Path
之外的其它类型将全部以空格分割。
@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 sub
和tool.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