随着AI的不断发展,CLI因为无GUI交互界面,直接传入参数,即可完成很多任务,非常方便,今天一起来学习在Python中如何开发CLI程序。
1.使用 sys.argv
利用 Python 自带的sys.argv 来快速传递参数,适用比较简单的程序且无需要记住参数名称的功能,但有一个缺点就是,参数得按顺序来进行传递。示例如下所示:
import sys
def add(a: int, b: int) -> int:
return int(a) + int(b)
if __name__ == '__main__':
if len(sys.argv) > 2:
a, b = sys.argv[1], sys.argv[2]
print(f"a+b={add(a, b)}")
运行结果如下所示:
$ uv run python sys_args.py 12 24
a+b=36
2.使用 argparse
这是Python自带的标准库,无须安装第三方依赖,功能相比于 sys.args 要更多。示例如下所示:
import argparse
def argparse_demo():
# 定义一个 ArgumentParse 实例
parse=argparse.ArgumentParser(
# 程序名称
prog="python-cli-demo",
# 使用说明
usage="python-cli-demo [command] [flag]",
# 描述信息
description="python-cli-demo 使用示例",
# 说明信息
epilog="Copyright(R) 2026 By Surpass"
)
# add_argument() 用于指定程序将能接受哪些命令行选项
# 定义位置参数
parse.add_argument("login",help="连接数据库")
# 定义关键字参数,数据类型为字符串类型
parse.add_argument("--ip",default="127.0.0.1",type=str,help="IP地址")
# 定义关键字参数,并允许用户输入短命名的参数
parse.add_argument("--port",default=3306,type=int,help="连接端口")
parse.add_argument("-u","--user",type=str,required=True,help="用户名")
parse.add_argument("-p","--password",type=str,required=True,help="密码")
parse.add_argument("-d","--database",default="demo_database",type=str,help="访问目标数据库")
# 表明该参数只有两个值 true 和 false ,如果指定了该项,则表示其值为 true ,未指定则表示为 false
parse.add_argument("-c","--compress",action="store_true",required=False,help="是否启用压缩模式")
# 给参数限定可选范围值
parse.add_argument("-v","--verbose",type=int,choices=[1,2,3],help="显示详细信息的级别,越大越详细")
# 解析参数
args=parse.parse_args()
# 打印传入的参数信息
arg_output_str=f"python-cli-demo {args.login} --ip {args.ip} --port {args.port} -u {args.user} -p {args.password} -d {args.database} -c {args.compress} -v {args.verbose}"
if (verbose_level:=args.verbose)==1:
print("当前显示详细级别为1")
elif verbose_level ==2:
print("当前显示详细级别为2")
elif verbose_level==3:
print("当前显示详细级别为3")
else:
print("显示详细级别输入错误")
print(f"DEBUG:连接字符串信息为:{arg_output_str}")
print("调用登录-Demo ...")
login_demo(ip=args.ip,port=args.port,user=args.user,password=args.password,database=args.database,is_compress=args.compress)
def login_demo(
ip:str,
port:int,
user:str,
password:str,
database:str,
is_compress:bool
):
if is_compress:
print(f"连接数据库,启用压缩模式,连接信息为:{user}:{password}@{ip}:{port} {database} {is_compress}")
return
print(f"连接数据库,未启用压缩模式,连接信息为:{user}:{password}@{ip}:{port} {database} {is_compress}")
if __name__ == "__main__":
argparse_demo()
运行结果如下所示:
# 测试第一种情况
$ uv run python std_argparse.py login --ip 192.168.9.10 --port 8080 -u surpass -p password -d demo -c -v 4
usage: python-cli-demo [command] [flag]
python-cli-demo: error: argument -v/--verbose: invalid choice: '4' (choose from '1', '2', '3')
# 测试第二种情况
$ uv run python std_argparse.py login --ip 192.168.9.10 --port 8080 -u surpass -p password -d demo -c -v 2
当前显示详细级别为2
DEBUG:连接字符串信息为:python-cli-demo login --ip 192.168.9.10 --port 8080 -u surpass -p password -d demo -c True -v 2
调用登录-Demo ...
连接数据库,启用压缩模式,连接信息为:surpass:password@192.168.9.10:8080 demo True
# 测试第三种情况
$ uv run python std_argparse.py login --ip 192.168.9.10 --port 8080 -u surpass -p password -d demo -v 2
当前显示详细级别为2
DEBUG:连接字符串信息为:python-cli-demo login --ip 192.168.9.10 --port 8080 -u surpass -p password -d demo -c False -v 2
调用登录-Demo ...
连接数据库,未启用压缩模式,连接信息为:surpass:password@192.168.9.10:8080 demo False
由于 argparse 是系统自带,依赖最少,参数解析能力完整,同时也支持 subcommand,可以满足一般性的要求。
3.使用 click 框架
click 是一款用于创建 CLI 程序的第三方框架,适合于存在多级命令、参数和选项很复杂、CLI有很多交互逻辑和高度可配置性的场景,click 官网地址。
3.1 安装
可以使用pip安装或uv安装,如下所示:
pip install click
uv add click
3.2 使用方法
click 使用方式非常简单,通常分为两步:
- 使用
@click.command()装饰函数,使其成为CLI接口 - 使用
@click.option()装饰函数,为其添加CLI选项
3.2.1 基本用法
import click
@click.command(name="重复打印某字符串")
@click.option("-c","--count",default=1,help="重复的次数")
@click.option("-s","--string",prompt="要重复的字符串",help="需要重复打印的字符串")
def repeat(string,count):
for i in range(1,count+1):
click.echo(f"repeate:{i} - {string}")
if __name__ == "__main__":
repeat()
运行结果如下所示:
# 测试第一种情况
$ uv run python click_demo.py -c 3 -s Surpass
repeate:1 - Surpass
repeate:2 - Surpass
repeate:3 - Surpass
# 测试第二种情况:交互式
$ uv run python click_demo.py -c 3
要重复的字符串: Surpass
repeate:1 - Surpass
repeate:2 - Surpass
repeate:3 - Surpass
# 测试第三种情况
$ uv run python click_demo.py
要重复的字符串: Surpass
repeate:1 - Surpass
# 查看帮助
$ uv run python click_demo.py --help
Usage: click_demo.py [OPTIONS]
Options:
-c, --count INTEGER 重复的次数
-s, --string TEXT 需要重复打印的字符串
--help Show this message and exit.
这里没有使用 python 自带的 print()。是因为click支持不同版本的 python,为了获得更好的兼容性和提供更丰富的功能(例如支持ANSI字体颜色的支持)。
click.option 基本用法就是通过指定CLI选项的名称,并从CLI中提取参数值,再将其传递给函数,常用的设置如下所示:
- default:CLI参数默认值
- help: 参数说明
- type: 参数类型
- prompt:在CLI中没有输入对应的参数时,会根据 prompt 信息提示用户输入
3.2.2 组合命令
click 可以通过group来创建命令行组,即可以通过各种参数来解决相同类别的不同问题。示例如下所示:
import click
@click.group()
def cli():
pass
@click.command()
def init_db():
click.echo("初始化数据库")
@click.command()
def drop_db():
click.echo("删除数据库")
cli.add_command(init_db)
cli.add_command(drop_db)
if __name__ == "__main__":
cli()
运行结果如下所示:
$ uv run python click_group.py
Usage: click_group.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
drop-db
init-db
$ uv run python click_group.py init-db
初始化数据库
$ uv run python click_group.py drop-db
删除数据库
对于一些比较简单的脚本,也可以使用group.command()自动附加并创建命令。通过修改装饰器,还可以按下这种方式来达到同样的效果。
import click
@click.group()
def cli():
pass
@cli.command()
def init_db():
click.echo("初始化数据库")
@cli.command()
def drop_db():
click.echo("删除数据库")
if __name__ == "__main__":
cli()
3.2.3 添加参数
如果需要添加参数,可以使用option()和argument装饰器,示例如下所示:
import click
@click.command()
@click.option("-c","--count",default=1,help="重复的次数")
@click.argument("string") # 添加参数
def repeat(string,count):
for i in range(1,count+1):
click.echo(f"repeate:{i} - {string}")
if __name__ == "__main__":
repeat()
注意运行结果与前面的区别,如下所示:
$ uv run click_args.py
Usage: click_args.py [OPTIONS] STRING
Try 'click_args.py --help' for help.
Error: Missing argument 'STRING'.
$ uv run click_args.py --help
Usage: click_args.py [OPTIONS] STRING
Options:
-c, --count INTEGER 重复的次数
--help Show this message and exit.
$ uv run click_args.py Surpass -c 4
repeate:1 - Surpass
repeate:2 - Surpass
repeate:3 - Surpass
repeate:4 - Surpass
4.使用 Typer
Typer 是一个用于构建 CLI 应用程序的库,基于 Python 类型提示,可以运行 python 程序并将其转换为 CLI 应用程序。Typer 官网
4.1 安装
可以使用pip安装或uv安装,如下所示:
pip install typer
uv add typer
4.2 使用
4.2.1 快速入门
在 Python 脚本中内部可以不使用 typer ,但可以使用 typer 命令将其转换为CLI应用程序运行
def greeting(name:str):
print(f"Hello, {name}")
通过 typer 转换为 CLI 应用程序,运行结果如下所示:
$ uv run typer typer_demo.py run --help
Usage: typer [PATH_OR_MODULE] run [OPTIONS] {name}
Run the provided Typer app.
╭─ Arguments ───────────────────────────────────────────────────────────────────╮
│ * name <str> [required] │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run typer typer_demo.py run Surpass
Hello, Surpass
除以上方式,还可以使用以下方式:
import typer
def greeting(name:str):
print(f"Hello, {name}")
if __name__ == "__main__":
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_demo.py Surpass
Hello, Surpass
4.2.2 参数
CLI中参数是指定特定顺序传递给CLI应用程序的CLI参数,默认情况,它们是必需的。在前面的示例中,我们已经了解如何添加CLI参数,现在再来看看另一种添加CLI参数的方法,示例如下所示:
import typer
from typing_extensions import Annotated
def greeting(name: Annotated[str, typer.Argument()]):
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_args.py Surpass
Hello,Surpass
4.2.2.1 可选参数
要使CLI参数可选,可以使用typer.Argument()将默认值作为第一个参数传递给typer.Argument(),如下所示:
from typing import Optional
import typer
from typing_extensions import Annotated
def greeting(name: Annotated[Optional[str], typer.Argument()]=None):
if name is None:
print(f"Hello,World")
else:
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_args.py
Hello,World
$ uv run python typer_args.py Surpass
Hello,Surpass
由于使用了
typer.Argument(),typer 就会知道这是一个CLI参数。
4.2.2.2 带默认值参数
可以使用 typer.Argument() 来设置默认参数值,这样就可以保证CLI参数是可选并具有默认值的。示例如下所示:
import typer
from typing_extensions import Annotated
def greeting(name: Annotated[str, typer.Argument()]="Surpass"):
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_default_args.py --help
Usage: typer_default_args.py [OPTIONS] [name]
╭─ Arguments ───────────────────────────────────────────────────────────────────╮
│ name <str> [default: Surpass] │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_default_args.py
Hello,Surpass
$ uv run python typer_default_args.py Evan
Hello,Evan
4.2.2.3 动态默认值参数
可以通过函数作为default_factory参数来传递动态默认值,示例如下所示:
import random
import typer
from typing_extensions import Annotated
def get_random_name()->str:
return random.choice(["Surpass","Evan","Kevin","Alice","Bob"])
def greeting(name: Annotated[str, typer.Argument(default_factory=get_random_name)]):
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_dynamic_args.py --help
Usage: typer_dynamic_args.py [OPTIONS] [name]
╭─ Arguments ───────────────────────────────────────────────────────────────────╮
│ name <str> [default: (dynamic)] │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_dynamic_args.py
Hello,Alice
$ uv run python typer_dynamic_args.py
Hello,Bob
4.2.3 选项
CLI中选项是指使用特定名称传递给CLI应用程序的参数,一般情况下,它们是可选的。在 typer 中可以使用typer.Option()来修改CLI选项。
4.2.3.1 基本用法
这里演示可以在CLI应用程序,使用类似--option或-p来传递参数,示例如下所示:
import typer
from typing_extensions import Annotated
def greeting(
name: Annotated[str, typer.Option("--name", "-n", help="姓名")],
formal: Annotated[bool, typer.Option("--formal", "-f", help="是否采用正式用法")] = False,
gender: Annotated[str, typer.Option("--gender", "-g", help="性别")] = "male"
):
if formal and gender == "male":
print(f"Good day Mr.{name}")
elif formal and gender == "female":
print(f"Good day Ms.{name}")
else:
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_name_option.py --help
Usage: typer_name_option.py [OPTIONS]
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ * --name -n <str> 姓名 [required] │
│ --formal -f 是否采用正式用法 │
│ --gender -g <str> 性别 [default: male] │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_name_option.py -n Surpass -f -g male
Good day Mr.Surpass
$ uv run python typer_name_option.py -n Surpass -f -g female
Good day Ms.Surpass
4.2.3.2 交互式用法
import typer
from typing_extensions import Annotated
def greeting(
name: Annotated[str, typer.Option("--name", "-n", help="姓名")],
email: Annotated[str, typer.Option("--email", prompt=True, confirmation_prompt=True, help="邮箱")]
):
print(f"Hello,{name},your email is: {email}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_interact.py --help
Usage: typer_interact.py [OPTIONS]
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ * --name -n <str> 姓名 [required] │
│ * --email <str> 邮箱 [required] │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_interact.py -n Surpass
Email: surpassme@surpassme.net
Repeat for confirmation: surpassme@surpassme.net
Hello,Surpass,your email is: surpassme@surpassme.net
4.2.3.3 选项回调
import typer
from typing_extensions import Annotated
def name_callback(name: str):
if name not in ["Surpass", "Evan", "Kevin"]:
raise typer.BadParameter(f"{name} is not allowed.Only Surpass,Evan,Kevin")
return name
def greeting(
name: Annotated[str, typer.Option("--name", "-n", callback=name_callback)]
):
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_option_callback.py --name Surpass
Hello,Surpass
$ uv run python typer_option_callback.py --name Alice
Usage: typer_option_callback.py [OPTIONS]
Try 'typer_option_callback.py --help' for help.
╭─ Error ───────────────────────────────────────────────────────────────────────╮
│ Invalid value for '--name' / '-n': Alice is not allowed.Only │
│ Surpass,Evan,Kevin │
╰───────────────────────────────────────────────────────────────────────────────╯
4.2.3.4 查看版本
from typing import Optional
import typer
from typing_extensions import Annotated
__version__: str = "1.0.0"
def name_callback(name: str):
if name not in ["Surpass", "Evan", "Kevin"]:
raise typer.BadParameter(f"{name} is not allowed.Only Surpass,Evan,Kevin")
return name
def version_callback(value: bool):
if value:
print(f"CLI Version:{__version__}")
raise typer.Exit()
def greeting(
name: Annotated[str, typer.Option("--name", "-n", callback=name_callback)],
version: Annotated[Optional[bool], typer.Option("--version", "-v", callback=version_callback)] = None
):
print(f"Hello,{name}")
if __name__ == '__main__':
typer.run(greeting)
运行结果如下所示:
$ uv run python typer_version.py --version
CLI Version:1.0.0
$ uv run python typer_version.py -v
CLI Version:1.0.0
4.2.4 子命令/命令组
在CLI命令通常还会存在一个或多个子命令的情况,例如uv run python ...等,这种情况被称之为子命令或命令组。
4.2.4.1 命令存在于多个文件
- items.py
import typer
from typing_extensions import Annotated
app = typer.Typer()
@app.command()
def create(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Create item: {item}")
@app.command()
def delete(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Delete item: {item}")
@app.command()
def sell(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Sell item: {item}")
if __name__ == '__main__':
app()
- user.py
import typer
from typing_extensions import Annotated
app = typer.Typer()
@app.command()
def create(username: Annotated[str, typer.Option("--username", "-u", help="用户名称")]):
print(f"Create username: {username}")
@app.command()
def delete(username: Annotated[str, typer.Option("--username", "-u", help="用户名称")]):
print(f"Delete username: {username}")
if __name__ == '__main__':
app()
- main.py
import typer
from typer_command_group import items, users
if __name__ == '__main__':
app = typer.Typer()
app.add_typer(items.app, name="items")
app.add_typer(users.app, name="users")
app()
运行结果如下所示:
$ uv run python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy │
│ it or customize the installation. │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────╮
│ items │
│ users │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python main.py items --help
Usage: main.py items [OPTIONS] COMMAND [ARGS]...
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────╮
│ create │
│ delete │
│ sell │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python main.py items create --help
Usage: main.py items create [OPTIONS]
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ * --item -i <str> 名称 [required] │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python main.py items create --item Gold
Create item: Gold
$ uv run python main.py users create -u Surpass
Create username: Surpass
4.2.4.2 命令存在单个文件中
import typer
from typing_extensions import Annotated
app = typer.Typer()
items_app = typer.Typer()
user_app = typer.Typer()
app.add_typer(items_app, name="items", help="items管理")
app.add_typer(user_app, name="users", help="用户管理")
@items_app.command(name="create", help="创建item")
def create(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Create item: {item}")
@items_app.command(name="delete", help="删除item")
def delete(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Delete item: {item}")
@items_app.command(name="sell", help="售卖item")
def sell(item: Annotated[str, typer.Option("--item", "-i", help="名称")]):
print(f"Sell item: {item}")
@user_app.command(name="create", help="创建用户")
def create(username: Annotated[str, typer.Option("--username", "-u", help="用户名称")]):
print(f"Create username: {username}")
@user_app.command(name="delete", help="删除用户")
def delete(username: Annotated[str, typer.Option("--username", "-u", help="用户名称")]):
print(f"Delete username: {username}")
if __name__ == '__main__':
app()
运行结果如下所示:
$ uv run python typer_command_group.py --help
Usage: typer_command_group.py [OPTIONS] COMMAND [ARGS]...
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy │
│ it or customize the installation. │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────╮
│ items items管理 │
│ users 用户管理 │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_command_group.py users --help
Usage: typer_command_group.py users [OPTIONS] COMMAND [ARGS]...
用户管理
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ────────────────────────────────────────────────────────────────────╮
│ create 创建用户 │
│ delete 删除用户 │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_command_group.py users delete --help
Usage: typer_command_group.py users delete [OPTIONS]
删除用户
╭─ Options ─────────────────────────────────────────────────────────────────────╮
│ * --username -u <str> 用户名称 [required] │
│ --help Show this message and exit. │
╰───────────────────────────────────────────────────────────────────────────────╯
$ uv run python typer_command_group.py users delete -u Surpass
Delete username: Surpass
$ uv run python typer_command_group.py items delete -i Gold
Delete item: Gold