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的struct模块中进行数据格式转换的方法
Jun 17 Python
Python复制文件操作实例详解
Nov 10 Python
Python 文件操作的详解及实例
Sep 18 Python
python写入已存在的excel数据实例
May 03 Python
Python DataFrame一列拆成多列以及一行拆成多行
Aug 06 Python
pycharm配置git(图文教程)
Aug 16 Python
python argparse模块通过后台传递参数实例
Apr 20 Python
python中urllib.request和requests的使用及区别详解
May 05 Python
使用Python将语音转换为文本的方法
Aug 10 Python
如何用 Python 制作 GitHub 消息助手
Feb 20 Python
Python办公自动化PPT批量转换操作
Sep 15 Python
Python 居然可以在 Excel 中画画你知道吗
Feb 15 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
php adodb连接mssql解决乱码问题
2009/06/12 PHP
PHP中的float类型使用说明
2010/07/27 PHP
WordPress中is_singular()函数简介
2015/02/05 PHP
1亿条数据如何分表100张到Mysql数据库中(PHP)
2015/07/29 PHP
js代码实现微博导航栏
2015/07/30 PHP
joomla实现注册用户添加新字段的方法
2016/05/05 PHP
提高Laravel应用性能方法详解
2019/06/24 PHP
Yii框架布局文件的动态切换操作示例
2019/11/11 PHP
利用XMLHTTP传递参数在另一页面执行并刷新本页
2006/10/26 Javascript
30个让人兴奋的视差滚动(Parallax Scrolling)效果网站
2012/03/04 Javascript
cument.execCommand()用法深入理解
2012/12/04 Javascript
div当滚动到页面顶部的时候固定在顶部实例代码
2013/05/27 Javascript
jquery按回车提交数据的代码示例
2013/11/05 Javascript
js 中将多个逗号替换为一个逗号的代码
2014/06/07 Javascript
Jquery实现仿腾讯微博发表广播
2014/11/17 Javascript
bootstrap下拉列表与输入框组结合的样式调整
2016/10/08 Javascript
jQuery实现的省市联动菜单功能示例【测试可用】
2017/01/13 Javascript
Jquery与Bootstrap实现后台管理页面增删改查功能示例
2017/01/22 Javascript
Vuex模块化实现待办事项的状态管理
2017/03/15 Javascript
JavaScript中一些特殊的字符运算
2017/08/17 Javascript
详解使用Visual Studio Code对Node.js进行断点调试
2017/09/14 Javascript
vue-cli之router基本使用方法详解
2017/10/17 Javascript
angular中不同的组件间传值与通信的方法
2017/11/04 Javascript
JavaScript设计模式之享元模式实例详解
2019/01/17 Javascript
基于Vue SEO的四种方案(小结)
2019/07/01 Javascript
ES6 proxy和reflect的使用方法与应用实例分析
2020/02/15 Javascript
Vue Cli3 打包配置并自动忽略console.log语句的方法
2020/04/23 Javascript
[01:46]新英雄登场
2019/09/10 DOTA
Pyqt QImage 与 np array 转换方法
2019/06/27 Python
Python Unittest原理及基本使用方法
2020/11/06 Python
HTML5 Canvas中使用路径描画二阶、三阶贝塞尔曲线
2015/01/01 HTML / CSS
html5利用canvas实现颜色容差抠图功能
2019/12/23 HTML / CSS
日本动漫周边服饰销售网站:Atsuko
2019/12/16 全球购物
Net Remoting把服务器端激活两种模式
2014/01/22 面试题
药品采购员岗位职责
2014/02/08 职场文书
学校领导四风问题整改措施思想汇报
2014/10/09 职场文书