Python3 Click模块的使用方法详解


Posted in Python onFebruary 12, 2020

Click 是 Flask 的团队 pallets 开发的优秀开源项目,它为命令行工具的开发封装了大量方法,使开发者只需要专注于功能实现。恰好我最近在开发的一个小工具需要在命令行环境下操作,就写个学习笔记。

国际惯例,先来一段 “Hello World” 程序(假定已经安装了 Click 包)。

# hello.py
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,不难猜到控制台的输出结果。除此之外,Click 还悄悄地做了其他的工作,比如帮助选项:

$ 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.

函数秒变 CLI

从上面的 “Hello World” 演示中可以看出,Click 是通过装饰器来把一个函数方法装饰成命令行接口的,这个装饰器方法就是 `@click.command()`。

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

`@click.command()装饰器把hello()方法变成了Command对象,当它被调用时,就会执行该实例内的行为。而?help参数就是Command` 对象内置的参数。

不同的 Command 实例可以关联到 group 中。group 下绑定的命令就成为了它的子命令,参考下面的代码:

@click.group()
def cli():
  pass
@click.command()
def initdb():
  click.echo('Initialized the database')
@click.command()
def dropdb():
  click.echo('Dropped the database')
cli.add_command(initdb)
cli.add_command(dropdb)

`@click.group装饰器把方法装饰为可以拥有多个子命令的Group对象。由Group.add_command()方法把Command对象关联到Group对象。 也可以直接用@Group.command装饰方法,会自动把方法关联到该Group` 对象下。

@click.group()
def cli():
  pass
@cli.command()
def initdb():
  click.echo('Initialized the database')
@cli.command()
def dropdb():
  click.echo('Dropped the database')

命令行的参数是不可或缺的,Click 支持对 command 方法添加自定义的参数,由 option() 和 argument() 装饰器实现。

@click.command()
@click.option('--count', default=1, help='number of greetings')
@click.argument('name')
def hello(count, name):
  for x in range(count):
    click.echo('Hello %s!' % name)

打包跨平台可执行程序

通过 Click 编写了简单的命令行方法后,还需要把 .py 文件转换成可以在控制台里运行的命令行程序。最简单的办法就是在文件末尾加上如下代码:

if __name__ == '__main__':
  command()

Click 支持使用 setuptools 来更好的实现命令行程序打包,把源码文件打包成系统中的可执行程序,并且不限平台。一般我们会在源码根目录下创建 setup.py 脚本,先看一段简单的打包代码:

from setuptools import setup
setup(
  name='hello',
  version='0.1',
  py_modules=['hello'],
  install_requires=[
    'Click',
  ],
  entry_points='''
    [console_scripts]
    hello=hello:cli
  ''',
)

留意 entry_points 字段,在 console_scripts 下,每一行都是一个控制台脚本,等号左边的的是脚本的名称,右边的是 Click 命令的导入路径。

详解命令行参数

上面提到了自定义命令行参数的两个装饰器:`@click.option()和@click.argument()`,两者有些许区别,使用场景也有所不同。

总体而言,argument() 装饰器比 option() 功能简单些,后者支持下面的特性:

  • 自动提示缺失的输入;
  • option 参数可以从环境变量中获取,argument 参数则不行;
  • option 参数在 help 输出中有完整的文档,argument 则没有;

而 argument 参数可以接受可变个数的参数值,而 option 参数只能接收固定个数的参数值(默认是 1 个)。

Click 可以设置不同的参数类型,简单类型如 click.STRING,click.INT,click.FLOAT,click.BOOL。

命令行的参数名由 “-short_name” 和 “?long_name” 声明,如果参数名既没有以 “-“ 开头,也没有以 “?” 开头,那么这边变量名会成为被装饰方法的内部变量,而非方法参数。

Option 参数

option 最基础的用法就是简单值变量,option 接收一个变量值,下面是一段示例代码:

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

如果在命令行后面跟随参数 --n=2 就会输出两个点,如果传参数,默认输出一个点。上面的代码中,参数类型没有显示给出,但解释器会认为是 INT 型,因为默认值 1 是 int 值。

有些时候需要传入多个值,可以理解为一个 list,option 只支持固定长度的参数值,即设置后必须传入,个数由 nargs 确定。

@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

既然可以传入 list,那么 tuple 呢?Click 也是支持的:

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

这样就传入了一个 tuple 变量,putitem --item peter 1338 得到的输出就是 name=peter id=1338

上面没有设置 nargs,因为 nargs 会自动取 tuple 的长度值。因此上面的代码实际上等同于:

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

option 还支持同一个参数多次使用,类似 git commit -m aa -m bb 中 -m 参数就传入了 2 次。option 通过 multiple 标识位来支持这一特性:

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

有时候,命令行参数是固定的几个值,这时就可以用到 Click.choice 类型来限定传参的潜在值:

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

当上面的命令行程序参数 --hash-type 不是 md5 或 sha1,就会输出错误提示,并且在 --help 提示中也会对 choice 选项有显示。

如果希望命令行程序能在我们错误输入或漏掉输入的情况下,友好的提示用户,就需要用到 Click 的 prompt 功能,看代码:

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

如果在执行 hello 时没有提供 ?name 参数,控制台会提示用户输入该参数。也可以自定义控制台的提示输出,把 prompt 改为自定义内容即可。

对于类似账户密码等参数的输入,就要进行隐藏显示。option 的 hide_input 和 confirmation_promt 标识就是用来控制密码参数的输入:

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

Click 把上面的操作进一步封装成装饰器 click.password_option(),因此上面的代码也可以简化成:

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

有的参数会改变命令行程序的执行,比如 node 是进入 Node 控制台,而 node --verion 是输出 node 的版本号。Click 提供 eager 标识对参数名进行标记,拦截既定的命令行执行流程,而是调用一个回调方法,执行后直接退出。下面模拟 click.version_option() 的功能,实现 --version 参数名输出版本号:

# eager
def print_version(ctx, param, value):
  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!')

对于类似删除数据库表这样的危险操作,Click 支持弹出确认提示,--yes 标识位置为 True 时会让用户再次确认:

# yes parameters
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 对次进行了封装,click.confirmation_option() 装饰器实现了上述功能:

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

前面只讲了默认的参数前缀 -- 和 -,Click 允许开发者自定义参数前缀(虽然严重不推荐)。

# other prefix
@click.command()
@click.option('+w/-w')
def chmod(w):
  click.echo('writable=%s' % w)
if __name__ == '__main__':
  chmod()

如果想要用 / 作为前缀,而且要像上面一样采用布尔标识,会产生冲突,因为布尔标识也是用 /,这种情况下可以用 ; 代替布尔标识的 /:

@click.command()
@click.option('/debug;/no-debug')
def log(debug):
  click.echo('debug=%s' % debug)
if __name__ == '__main__':
  log()

既然支持 Choice,不难联想到 Range,先看代码:

# range
@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()

Argument 参数

Argument 的作用类似 Option,但没有 Option 那么全面的功能。

和 Option 一样,Argument 最基础的应用就是传递一个简单变量值:

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

命令行后跟的参数值被赋值给参数名 filename。

另一个用的比较广泛的是可变参数,也是由 nargs 来确定参数个数,变量值会以 tuple 的形式传入函数:

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

运行程序:

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

Click 支持通过文件名参数对文件进行操作,click.File() 装饰器就是处理这种操作的,尤其是在类 Unix 系统下,它支持以 - 符号作为标准输入/输出。

# 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)

运行程序,先将文本写进文件,再读取

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

如果参数值只是想做为文件名而已呢,很简单,将 type 指定为 click.Path():

@click.command()
@click.argument('f', type=click.Path(exists=True))
def touch(f):
  click.echo(click.format_filename(f))
$ touch hello.txt
hello.txt
$ touch missing.txt
Usage: touch [OPTIONS] F
Error: Invalid value for "f": Path "missing.txt" does not exist.

更多关于Python3 Click模块的使用方法请查看下面的相关链接

Python 相关文章推荐
python获得图片base64编码示例
Jan 16 Python
使用python编写批量卸载手机中安装的android应用脚本
Jul 21 Python
Python实现扫描局域网活动ip(扫描在线电脑)
Apr 28 Python
Python中operator模块的操作符使用示例总结
Jun 28 Python
Python模糊查询本地文件夹去除文件后缀的实例(7行代码)
Nov 09 Python
python求最大连续子数组的和
Jul 07 Python
python opencv将图片转为灰度图的方法示例
Jul 31 Python
python 3.6.7实现端口扫描器
Sep 04 Python
tensorflow实现测试时读取任意指定的check point的网络参数
Jan 21 Python
Tensorflow 实现将图像与标签数据转化为tfRecord文件
Feb 17 Python
OpenCV4.1.0+VS2017环境配置的方法步骤
Jul 09 Python
Python连接mysql方法及常用参数
Sep 01 Python
pyecharts绘制中国2020肺炎疫情地图的实例代码
Feb 12 #Python
多个python文件调用logging模块报错误
Feb 12 #Python
Python对Tornado请求与响应的数据处理
Feb 12 #Python
在PyCharm中实现添加快捷模块
Feb 12 #Python
Python的赋值、深拷贝与浅拷贝的区别详解
Feb 12 #Python
解决pyCharm中 module 调用失败的问题
Feb 12 #Python
Python写出新冠状病毒确诊人数地图的方法
Feb 12 #Python
You might like
ThinkPHP利用PHPMailer实现邮件发送实现代码
2013/09/26 PHP
php使用curl模拟登录后采集页面的例子
2013/11/04 PHP
PHP GD库生成图像的几个函数总结
2014/11/19 PHP
js的event详解。
2006/09/06 Javascript
jquery控制listbox中项的移动并排序的实现代码
2010/09/28 Javascript
jQuery响应enter键的实现思路
2014/04/18 Javascript
js实现单击图片放大图片的方法
2015/02/17 Javascript
纯javascript移动优先的幻灯片效果
2015/11/02 Javascript
jquery中ajax跨域方法实例分析
2015/12/18 Javascript
js判断图片加载完成后获取图片实际宽高的方法
2016/02/25 Javascript
js 转义字符及URI编码详解
2017/02/28 Javascript
d3绘制基本的柱形图的实现代码
2018/12/12 Javascript
JavaScript函数式编程(Functional Programming)纯函数用法分析
2019/05/22 Javascript
通过jQuery学习js类型判断的技巧
2019/05/27 jQuery
Vue触发隐藏input file的方法实例详解
2019/08/14 Javascript
vant IndexBar实现的城市列表的示例代码
2019/11/20 Javascript
vue+ts下对axios的封装实现
2020/02/18 Javascript
快速解决Vue、element-ui的resetFields()方法重置表单无效的问题
2020/08/12 Javascript
通过JS判断网页是否为手机打开
2020/10/28 Javascript
python利用beautifulSoup实现爬虫
2014/09/29 Python
Python中super关键字用法实例分析
2015/05/28 Python
Python只用40行代码编写的计算器实例
2017/05/10 Python
不可错过的十本Python好书
2017/07/06 Python
django用户注册、登录、注销和用户扩展的示例
2018/03/19 Python
Python之lambda匿名函数及map和filter的用法
2019/03/05 Python
解决Atom安装Hydrogen无法运行python3的问题
2019/08/28 Python
Python进阶之使用selenium爬取淘宝商品信息功能示例
2019/09/16 Python
区域总监的岗位职责
2013/11/21 职场文书
商务会议邀请函
2014/01/09 职场文书
中学校庆方案
2014/03/17 职场文书
ktv筹备计划书
2014/05/03 职场文书
颐和园的导游词
2015/01/30 职场文书
小学大队委竞选口号
2015/12/25 职场文书
2019年描写人生经典诗句大全
2019/07/08 职场文书
Python写情书? 10行代码展示如何把情书写在她的照片里
2022/04/21 Python
html用代码制作虚线框怎么做? dw制作虚线圆圈的技巧
2022/12/24 HTML / CSS