Python命令行工具
1. 引言
1.1 背景
在Python编程领域,命令行工具作为一种重要的用户交互方式,为程序提供了灵活性和便捷性。随着Python应用的多样化,命令行工具的需求也日益增加。
1.2 目的
旨在对Python命令行工具进行深入的调研和分析,以便开发者能够根据实际需求选择合适的工具,提高开发效率和用户体验。
2. Python命令行工具类型
在Python中,有多个用于创建命令行界面的库。optparse
、argparse
、click
和fire
是其中的四个。
2.1. optparse (已弃用)
概述:
optparse
是Python 2中的一个旧库,用于解析命令行选项。- 在Python 2.7之后的版本中,官方推荐使用
argparse
替代optparse
。
特点:
- 相对简单,易于上手。
- 不支持某些现代CLI特性,如子命令。
- 已不再维护,不推荐新项目使用。
2.2. argparse
概述:
argparse
是Python标准库的一部分,自Python 2.7起可用,是optparse
的替代品。- 用于编写用户友好的命令行接口。
- 支持子命令、参数、选项以及帮助信息的自动生成。
特点:
- 功能强大,能够满足大多数CLI需求。
- 自动处理
-h
/--help
选项。 - 支持参数类型检查和默认值设置。
- 通过继承
argparse.ArgumentParser
可以扩展功能。
2.3. click
概述:
click
是一个第三方库,旨在通过声明式的方式创建复杂的命令行界面。- 由Armin Ronacher开发,是Flask项目的一部分。
- 提供了装饰器和上下文管理来简化CLI的开发。
特点:
- 强调可读性和简洁性。
- 支持命令、选项、参数和子命令的声明式定义。
- 提供了丰富的内置类型和验证器。
- 良好的文档和社区支持。
2.4. fire
概述:
fire
是一个轻量级的Python CLI库,由Google开发。- 旨在简化从Python函数创建命令行界面的过程。
- 提供了简单的API来注册函数作为命令行操作。
特点:
- 极简主义设计,易于集成到现有代码中。
- 自动生成帮助信息。
- 支持参数和子命令。
- 适合快速原型开发和小项目。
3. 常用Python命令行工具库介绍
3.1 optparse(弃用)
API函数 | 描述 | 示例 |
---|---|---|
add_option | 添加一个新的命令行选项 | parser.add_option(“-f”, “–file”, dest=“filename”, help=“…”) |
add_options | 添加多个命令行选项 | parser.add_options(opt1, opt2, …) |
parse_args | 解析命令行参数 | (options, args) = parser.parse_args() |
get_option | 获取特定选项的值 | value = options.get_option(“–file”) |
print_help | 打印帮助信息 | parser.print_help() |
error | 输出错误信息并退出 | parser.error(“Invalid input”) |
示例
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-f", "--file", dest="filename", help="read data from FILE", metavar="FILE")
parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="don't print status messages to stdout")
(options, args) = parser.parse_args()
if options.verbose:
print("verbosity is on")
if options.filename:
print(f"read data from file: {options.filename}")
3.2 argparse
argparse 常用API
API函数 | 描述 | 示例 |
---|---|---|
add_argument | 添加一个新的命令行参数 | parser.add_argument(‘integers’, metavar=‘N’, type=int, nargs=‘+’, help=‘…’) |
add_mutually_exclusive_group | 添加互斥参数组 | group = parser.add_mutually_exclusive_group(required=True) |
add_subparsers | 添加子解析器 | subparsers = parser.add_subparsers(dest=‘command’) |
parse_known_args | 解析已知参数 | args, unknown = parser.parse_known_args() |
set_defaults | 设置默认参数值 | parser.set_defaults(verbose=True) |
print_usage | 打印用法信息 | parser.print_usage() |
print_help | 打印帮助信息 | parser.print_help() |
exit | 退出程序 | parser.exit(status=0, message=“…”) |
add_argument 参数
参数名称 | 描述 |
---|---|
name or flags | 一个命名或者一个选项字符串的列表,例如 foo 或 -f, --foo。 |
action | 当参数在命令行中出现时使用的动作基本类型。 |
nargs | 命令行参数应当消耗的数目。 |
const | 被一些 action 和 nargs 选择所需求的常数。 |
default | 当参数未在命令行中出现并且也不存在于命名空间对象时所产生的值。 |
type | 命令行参数应当被转换成的类型。 |
choices | 由允许作为参数的值组成的序列。 |
required | 此命令行选项是否可省略 (仅选项可用)。 |
help | 一个此选项作用的简单描述。 |
metavar | 在使用方法消息中使用的参数值示例。 |
dest | 被添加到 parse_args() 所返回对象上的属性名。 |
示例
import argparse
# 创建 ArgumentParser 对象
parser = argparse.ArgumentParser(description="这是一个命令行参数示例")
# 添加命令行参数
parser.add_argument("-n", "--name", action="store", type=str, required=True, help="输入你的名字", dest="username")
parser.add_argument("-a", "--age", action="store", type=int, default=18, help="输入你的年龄", dest="userage")
parser.add_argument("-g", "--gender", action="store", choices=["male", "female", "other"], type=str, default="other", help="输入你的性别", dest="usergender")
# 解析命令行参数
args = parser.parse_args()
# 输出解析后的参数值
print(f"用户名: {args.username}")
print(f"年龄: {args.userage}")
print(f"性别: {args.usergender}")
在这个示例中,我们定义了三个命令行参数:-n
或 --name
、-a
或 --age
和 -g
或 --gender
。以下是每个参数的详细说明:
参数名称 | 描述 | 值/示例 |
---|---|---|
name or flags | -n 或 --name | 张三 |
action | store | (默认) |
type | str | (默认) |
required | True | 是 |
help | 输入你的名字 | |
dest | username |
参数名称 | 描述 | 值/示例 |
---|---|---|
name or flags | -a 或 --age | 25 |
action | store | (默认) |
type | int | (默认) |
default | 18 | |
help | 输入你的年龄 | |
dest | userage |
参数名称 | 描述 | 值/示例 |
---|---|---|
name or flags | -g 或 --gender | male |
action | store | (默认) |
type | str | (默认) |
choices | [“male”, “female”, “other”] | |
default | other | |
help | 输入你的性别 | |
dest | usergender |
3.3 click
Click 实际上是通过对 optparse 的一个分支进行封装而实现的,它本身并不进行解析。不基于 argparse 的原因是 argparse 从设计上不允许多个命令的嵌套, 同时在兼容处理 POSIX 参数时有一些不足。
click 常用API
API函数 | 描述 | 示例 |
---|---|---|
command | 定义一个新的命令 | @click.command() |
option | 定义一个新的命令行选项 | @click.option(‘–count’, default=1, help=‘…’) |
argument | 定义一个新的命令行参数 | @click.argument(‘name’) |
pass_context | 传递上下文给命令函数 | @click.command(pass_context=True) |
CommandCollection | 将命令集合起来,形成完整的命令行接口 | if name == ‘main’: click.CommandCollection(info) |
Group | 创建一个命令组 | group = click.Group(‘group_name’) |
MultiCommand | 创建一个多命令对象 | multi = click.MultiCommand(‘multi’, ‘Handle multiple commands.’) |
Click Options
Click 库提供了丰富的命令行选项定义方式,可以帮助开发者轻松地创建功能强大且易于使用的命令行应用程序。
选项命名
- 选项名称可以用短横线(-)或双短横线(–)前缀,也可以直接使用参数名。
- Click 会根据选项的定义顺序选择合适的选项名称,并将其转换为 Python 参数名。
基本选项类型
- 数值选项: 接受一个参数,可以指定默认值和数据类型,默认类型为字符串。
- 必需选项: 使用
required=True
将选项设置为必需的,用户必须在命令行中提供值。 - 显示默认值: 使用
show_default=True
在帮助信息中显示选项的默认值。
多值选项
- 固定数量参数: 使用
nargs=n
定义接受固定数量参数的选项,参数值将以元组形式存储。 - 可变数量参数: 使用
multiple=True
定义可以接受多次相同选项的选项,参数值将以列表形式存储。 - 计数: 使用
count=True
定义可以重复使用的选项,重复次数将被记录为整数。
布尔选项
- 使用
/
分隔两个选项,分别表示启用和禁用选项,例如--shout/--no-shout
。 - 可以使用
is_flag=True
手动指定选项为布尔选项。
特性选项
- 可以使用
flag_value
参数定义特性选项,例如--upper/--lower
。
选择选项
- 使用
click.Choice
类型定义可以从多个选项中选择的值,例如--hash-type=MD5
。
提示输入
- 使用
prompt
参数定义提示用户输入的选项,如果用户未在命令行中提供值,Click 将提示输入。 - 使用
hide_input=True
定义隐藏输入的选项,例如密码输入。 - 使用
password_option()
装饰器简化密码输入选项的定义。
动态默认值
- 可以使用可调用对象作为默认值,以便根据环境或配置动态获取默认值。
回调函数和优先处理选项
- 使用
is_eager=True
定义优先处理的选项,这些选项将在其他选项之前处理。 - 使用
callback
参数定义回调函数,用于在选项处理完成后执行特定操作。
确认选项
- 使用
confirmation_option()
装饰器或--yes
选项,要求用户确认操作。
环境变量
- 使用
auto_envvar_prefix
参数定义环境变量前缀,自动创建对应环境变量。 - 使用
envvar
参数指定环境变量名,手动获取环境变量的值。
其他前缀字符
- 可以使用
prefix_chars
参数定义其他前缀字符,例如/
。
范围选项
- 使用
IntRange
和FloatRange
类型定义值必须在特定范围内。
回调函数验证
- 使用
callback
参数定义验证函数,对选项值进行自定义验证。
可选值
- 使用
is_flag=False
和flag_value
定义可选值的选项,如果仅使用选项名,则使用flag_value
作为值。 - 使用
prompt_required=False
限制提示输入的条件。
综合示例
import click
from click import Choice, IntRange, password_option
# 动态默认值
def get_default_name():
return "World"
# 回调函数验证
def validate_name(ctx, param, value):
if not value.isalpha():
raise click.BadParameter("Name must contain only letters.")
return value.upper()
# 确认选项装饰器
def confirmation_option(*param_decls, **attrs):
def decorator(f):
attrs.setdefault('prompt', 'Are you sure?')
attrs.setdefault('is_flag', True)
attrs.setdefault('default', False)
attrs.setdefault('show_default', True)
attrs.setdefault('help', 'Confirm the action.')
click.option(*param_decls, **attrs)(f)
return f
return decorator
@click.command()
@click.argument('name', required=True, callback=validate_name)
@click.option('-a', '--age', type=int, default=18, show_default=True, help="Age of the person.")
@click.option('--upper/--lower', default=None, help="Print uppercase or lowercase.")
@click.option('--shout/--no-shout', default=False, help="Shout the message.")
@click.option('--hash-type', type=Choice(['MD5', 'SHA1', 'SHA256']), help="Choose a hash algorithm.")
@click.option('--password', hide_input=True, help="Enter a password.")
@click.option('--password2', is_flag=True, flag_value='password', prompt_required=False, help="Use this as the password.")
@password_option(help="Enter a secure password.")
@click.option('--repeat', count=True, help="Repeat the message.")
@click.option('--number', nargs=2, type=int, help="Specify two numbers.")
@click.option('--numbers', multiple=True, type=int, help="Specify multiple numbers.")
@click.option('--range', type=IntRange(1, 100), help="Specify a number between 1 and 100.")
@click.option('--confirm', is_flag=True, callback=lambda ctx, param, value: value if value else ctx.abort(), help="Confirm the action.")
@confirmation_option('--yes', help="Confirm without prompting.")
@click.option('--default-name', default=get_default_name, help="Default name.")
@click.option('--prompt-name', prompt=True, help="Prompt for a name.")
@click.option('--env-name', envvar='USER_NAME', help="Name from environment variable.")
@click.option('--prefix-name', '/name', help="Name with custom prefix.")
def greet(name, age, upper, shout, hash_type, password, password2, password3, repeat, number, numbers, _range, confirm, yes, default_name, prompt_name, env_name, prefix_name):
# 处理参数
if shout:
name = name.upper()
elif upper:
name = name.upper()
elif upper is False:
name = name.lower()
# 处理哈希类型
if hash_type:
click.echo(f"Selected hash type: {hash_type}")
# 处理密码
if password:
click.echo("Password provided.")
if password2:
click.echo(f"Password set to: {password2}")
if password3:
click.echo("Secure password provided.")
# 重复输出
for i in range(repeat):
click.echo(f"Hello, {name}! You are {age} years old.")
# 输出数字
if number:
click.echo(f"Numbers: {number}")
if numbers:
click.echo(f"Numbers list: {numbers}")
# 输出范围内的数字
if _range:
click.echo(f"Range: {_range}")
# 确认动作
if confirm or yes:
click.echo("Action confirmed.")
else:
click.echo("Action not confirmed.")
# 显示默认值
click.echo(f"Default name: {default_name}")
# 显示提示输入的值
click.echo(f"Prompt name: {prompt_name}")
# 显示环境变量的值
click.echo(f"Environment name: {env_name}")
# 显示带有自定义前缀的值
click.echo(f"Prefix name: {prefix_name}")
if __name__ == '__main__':
greet()
示例解释
-
必需参数 (
name
):- 使用
validate_name
回调函数验证名字只包含字母,并将其转换为大写。
- 使用
-
基本选项 (
age
):- 具有默认值和帮助信息。
-
布尔选项 (
upper
,shout
):- 控制输出的大写或小写。
-
选择选项 (
hash-type
):- 选择加密算法。
-
提示输入 (
prompt-name
):- 如果用户没有提供值,程序会提示输入。
-
环境变量 (
env-name
):- 从环境变量
USER_NAME
获取值。
- 从环境变量
-
多值选项 (
numbers
):- 用户可以多次使用相同的选项来提供多个值。
-
计数 (
repeat
):- 重复输出消息。
-
范围选项 (
_range
):- 用户必须提供一个介于 1 到 100 之间的整数。
-
确认选项 (
confirm
,--yes
):- 用户必须确认动作才能继续。
-
动态默认值 (
default-name
):- 根据函数
get_default_name
返回的值设置默认值。
- 根据函数
-
其他前缀字符 (
prefix-name
):- 使用
/
作为前缀字符。
- 使用
-
密码输入 (
password
,password2
,password3
):- 使用
hide_input=True
隐藏输入,以及password_option
装饰器简化密码输入。
- 使用
-
回调函数验证 (
validate_name
):- 验证名字只包含字母。
3.4 fire
fire 常见API
设置 | 命令 | 备注 |
---|---|---|
安装 | pip install fire | 从PyPI安装Fire |
创建命令行界面 | 命令 | 备注 |
---|---|---|
导入 | import fire | |
调用 | fire.Fire() | 将当前模块转换为Fire命令行界面(CLI)。 |
调用 | fire.Fire(component) | 将component 转换为Fire命令行界面(CLI)。 |
使用命令行界面 | 命令 | 备注 |
---|---|---|
帮助 | command --help | 显示帮助页面。 |
交互式模式 | command -- --interactive | 进入交互模式。 |
分隔符 | command -- --separator=X | 此设置将分隔符设为X 。默认分隔符是- 。 |
补全脚本 | command -- --completion [shell] | 为CLI生成补全脚本。 |
追踪 | command -- --trace | 获取命令的Fire追踪信息。 |
详细输出 | command -- --verbose |
请注意,标志与Fire命令之间通过孤立的--
参数进行分隔。
帮助是个例外;获取帮助时孤立的--
是可选的。
调用fire.Fire()
的参数
参数 | 用法 | 备注 |
---|---|---|
component | fire.Fire(component) | 如果省略,则默认为所有局部和全局变量的字典。 |
command | fire.Fire(command='hello --name=5') | 可以是一个字符串或一个参数列表。如果提供的是字符串,则对其进行拆分以确定参数。如果提供的是列表或元组,则它们就是参数。如果省略command ,则默认使用sys.argv[1:] (即命令行中的参数)。 |
name | fire.Fire(name='tool') | CLI的名称,理想情况下是用户运行CLI时输入的名称。此名称将用于CLI的帮助页面中。如果省略该参数,则会自动推断。 |
serialize | fire.Fire(serialize=custom_serializer) | 如果省略,则简单的类型通过其内置的str 方法进行序列化,定义了自定义__str__ 方法的任何对象通过该方法进行序列化。如果指定了,所有对象都将通过提供的方法进行文本序列化。 |
不修改任何代码即可使用Fire CLI
您可以在不修改模块代码的情况下使用Python Fire。
语法如下:
python -m fire <module> <arguments>
或者
python -m fire <filepath> <arguments>
例如,python -m fire calendar -h
将把内置的calendar
模块当作CLI处理并提供其帮助信息。
示例
这个程序将包括自定义序列化、构造函数参数传递、函数参数传递、以及对*varargs
和**kwargs
的支持。此外,它还将展示如何处理不同类型的输入数据,包括布尔值。
import fire
class Building:
def __init__(self, name, stories=1, floors_per_story=10):
self.name = name
self.stories = stories
self.floors_per_story = floors_per_story
def __str__(self):
return f"{self.name} has {self.stories} stories and {self.floors_per_story} floors per story."
def climb_stairs(self, stairs_per_floor=10):
"""
Climb through each floor of each story in the building.
Yields each step climbed, and messages at the end of each story and the entire building.
"""
for story in range(self.stories):
for floor in range(self.floors_per_story):
for stair in range(stairs_per_floor):
yield stair
yield 'Phew!' # At the end of each floor
yield 'Done with this story!' # At the end of each story
yield 'Done with the whole building!' # At the end of the building
def order_by_length(*items, reverse=False):
"""
Orders items by length, breaking ties alphabetically.
Optionally reverses the sort order.
"""
sorted_items = sorted(items, key=lambda item: (len(str(item)), str(item)), reverse=reverse)
return ' '.join(sorted_items)
def type_of(obj):
"""
Prints the type of the given object.
"""
return type(obj).__name__
if __name__ == '__main__':
fire.Fire({
'building': Building,
'order': order_by_length,
'type': type_of
})
使用说明
-
实例化建筑并爬楼梯:
- 创建一个建筑实例并指定参数。
- 调用
climb_stairs
函数并指定楼梯每层的数量。 - 例如:
$ python example.py building --name="Sherrerd Hall" --stories=3 --floors_per_story=5 climb_stairs --stairs_per_floor=10
-
按长度排序项目:
- 传递多个项目并可选择性地反转排序。
- 例如:
$ python example.py order cat dog elephant --reverse
-
获取对象类型:
- 传递一个对象以确定其类型。
- 例如:
$ python example.py type [1, 2, 3]
示例运行
实例化建筑并爬楼梯
$ python example.py building --name="Sherrerd Hall" --stories=3 --floors_per_story=5 climb_stairs --stairs_per_floor=10
0
1
2
3
4
5
6
7
8
9
Phew!
0
1
2
3
4
5
6
7
8
9
Phew!
0
1
2
3
4
5
6
7
8
9
Phew!
Done with this story!
0
1
2
3
4
5
6
7
8
9
Phew!
0
1
2
3
4
5
6
7
8
9
Phew!
Done with this story!
0
1
2
3
4
5
6
7
8
9
Phew!
Done with the whole building!
按长度排序项目
$ python example.py order cat dog elephant --reverse
elephant dog cat
获取对象类型
$ python example.py type "(1, 2)"
tuple
4. 性能对比
特性/工具 | optparse | argparse | click | fire |
---|---|---|---|---|
标准库 | 是 | 是 | 否 | 否 |
易用性 | 简单 | 中等 | 简单 | 非常简单 |
功能性 | 基础 | 高级 | 高级 | 基础至高级 |
可读性 | 良好 | 良好 | 非常好 | 良好 |
维护状态 | 不推荐使用 | 推荐使用 | 活跃维护 | 活跃维护 |
社区支持 | 有限 | 广泛 | 广泛 | 逐渐增长 |
optparse
由于功能限制和已弃用的状态,不推荐新项目使用。argparse
是当前的标准选择,提供了强大的功能和良好的用户支持。click
以其简洁和声明式的方式,适合快速开发和轻量级应用。fire
以其简单易用,适合快速创建基于函数的命令行工具。
分析
- 维护状态:
optparse
已弃用,不推荐使用。argparse
是标准库的一部分,持续得到维护和更新。click
和fire
都是活跃的第三方库,有持续的更新和社区支持。 - 学习曲线:
optparse
和argparse
相对容易上手,但argparse
提供了更多的功能和灵活性。click
通过装饰器和上下文管理简化了CLI的开发,而fire
则提供了一个极简的API。 - 功能:
argparse
、click
和fire
都提供了创建复杂CLI的能力,包括参数、选项和子命令的支持。optparse
在这方面较为有限。 - 社区和文档:
click
和argparse
有较好的社区支持和文档。fire
虽然是由Google开发,但其文档和社区支持也相对完善。
结论
- 对于新项目,推荐使用
argparse
、click
或fire
。argparse
作为标准库的一部分,具有稳定性和广泛的支持。click
和fire
则提供了更现代和灵活的CLI开发方式。 optparse
由于其已弃用的状态,不推荐用于新项目。