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系列之新版本导入httplib模块报ImportError解决方案
May 23 Python
详解python的webrtc库实现语音端点检测
May 31 Python
Python利用ElementTree模块处理XML的方法详解
Aug 31 Python
Python3.遍历某文件夹提取特定文件名的实例
Apr 26 Python
pycharm执行python时,填写参数的方法
Oct 29 Python
通过PYTHON来实现图像分割详解
Jun 26 Python
python数据库开发之MongoDB安装及Python3操作MongoDB数据库详细方法与实例
Mar 18 Python
django创建超级用户时指定添加其它字段方式
May 14 Python
Python如何定义有可选参数的元类
Jul 31 Python
python生成xml时规定dtd实例方法
Sep 21 Python
基于Python实现粒子滤波效果
Dec 01 Python
Python 详解通过Scrapy框架实现爬取百度新冠疫情数据流程
Nov 11 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
要会喝咖啡也要会知道咖啡豆
2021/03/03 咖啡文化
文章推荐系统(三)
2006/10/09 PHP
php 全局变量范围分析
2009/08/07 PHP
一漂亮的PHP图片验证码实例
2014/03/21 PHP
yii实现图片上传及缩略图生成的方法
2014/12/04 PHP
php输出xml属性的方法
2015/03/19 PHP
php 在线导入mysql大数据程序
2015/06/11 PHP
PHP二维数组排序简单实现方法
2016/02/14 PHP
PHP面向对象五大原则之依赖倒置原则(DIP)详解
2018/04/08 PHP
FusionCharts图表显示双Y轴双(多)曲线
2012/11/22 Javascript
cookie的复制与使用记住用户名实现代码
2013/11/04 Javascript
jquery限定文本框只能输入数字即整数和小数
2013/11/29 Javascript
JavaScript代码应该放在HTML代码哪个位置比较好?
2014/10/16 Javascript
非jQuery实现照片散落桌子上,单击放大的LightBox效果
2014/11/28 Javascript
浅谈javascript的Array.prototype.slice.call
2015/08/31 Javascript
JS实现的简洁二级导航菜单雏形效果
2015/10/13 Javascript
探讨JavaScript语句的执行过程
2016/01/28 Javascript
Vue 2.0学习笔记之使用$refs访问Vue中的DOM
2017/12/19 Javascript
详解Chai.js断言库API中文文档
2018/01/31 Javascript
Angular 4.x+Ionic3踩坑之Ionic 3.x界面传值详解
2018/03/13 Javascript
element UI upload组件上传附件格式限制方法
2018/09/04 Javascript
详解vue 自定义marquee无缝滚动组件
2019/04/09 Javascript
微信小程序云开发如何使用npm安装依赖
2019/05/18 Javascript
解决vue项目axios每次请求session不一致的问题
2020/10/24 Javascript
浅谈vue在html中出现{{}}的原因及解决方式
2020/11/16 Javascript
[00:34]DOTA2上海特级锦标赛 Spirit战队宣传片
2016/03/04 DOTA
Python实现给文件添加内容及得到文件信息的方法
2015/05/28 Python
使用Python来编写HTTP服务器的超级指南
2016/02/18 Python
Python中类型检查的详细介绍
2017/02/13 Python
numpy.random模块用法总结
2019/05/27 Python
PyQt5.6+pycharm配置以及pyinstaller生成exe(小白教程)
2020/06/02 Python
可以随进度显示不同颜色的css3进度条分享
2014/04/11 HTML / CSS
利用CSS3实现开门效果实例源码
2016/08/22 HTML / CSS
使用HTML5原生对话框元素并轻松创建模态框组件
2019/03/06 HTML / CSS
毕业生求职推荐信
2013/11/04 职场文书
采购助理岗位职责
2014/02/16 职场文书