装饰器 · Click CLI

Click 通过装饰器把 Python 函数变成命令行接口(CLI,Command Line Interface)。本文按子装饰器分节,每节含参数表与可运行示例。

安装pip install click

0. 一句话定位

维度 内容
作用对象 函数
使用场景 CLI
来源 第三方 click(≥8.x)
语法形式 @click.group / @click.command / @click.option / @click.argument

段末注释:Click 是 Python 下构建 CLI 的库,装饰器顺序通常为:最靠近 def 的是 @click.command(),其上是 @click.option / @click.argument


1. 装饰器组合顺序

1
2
3
4
5
@click.command()
@click.option("--verbose", is_flag=True)
@click.argument("name")
def hello(name, verbose):
...

等价理解:从下到上包装,command 最内层,option/argument 在外层注册参数。


2. @click.command

将函数注册为单条 CLI 命令

重点参数

参数 类型 默认值 作用 配置建议
name str 函数名 命令名 与函数名不一致时指定,如 name="run"
help str docstring 首段 帮助正文 写清命令用途
short_help str 截断的 help --help 列表中的短说明 子命令多时建议填
epilog str None 帮助末尾附加说明 放示例 invocation
no_args_is_help bool False 无参数调用时显示 help 纯子命令 group 的 leaf 可设 True
context_settings dict None 传给 Context {"max_content_width": 120}

最小示例

1
2
3
4
5
6
7
8
9
import click

@click.command(help="向 NAME 打招呼")
@click.argument("name")
def hello(name):
click.echo(f"Hello, {name}!")

if __name__ == "__main__":
hello()
1
2
3
4
python hello_cli.py World
# Hello, World!

python hello_cli.py --help

3. @click.group

将函数注册为命令组,可挂载多条子命令。

重点参数

参数 类型 默认值 作用 配置建议
invoke_without_command bool False 无子命令时是否执行 group 回调 需要 cli() 本身有逻辑时设 True
chain bool False 允许多子命令链式调用 管道式工具链,见 §10
help / short_help str 组级帮助 command 相同

最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import click

@click.group()
def cli():
"""示例 CLI 工具集"""
pass

@cli.command()
@click.argument("name")
def hello(name):
click.echo(f"Hello, {name}")

@cli.command()
def version():
click.echo("1.0.0")

if __name__ == "__main__":
cli()
1
2
3
4
python cli_group.py hello Alice
# Hello, Alice

python cli_group.py --help

嵌套 group

1
2
3
4
5
6
7
8
9
10
11
12
@click.group()
def cli():
pass

@cli.group()
def db():
"""数据库操作"""
pass

@db.command("init")
def db_init():
click.echo("DB initialized")

调用:python app.py db init


4. @click.option

为命令添加可选参数(一般以 -- / - 开头)。

重点参数

参数 类型 默认值 作用 配置建议
名称 str 必填 "--count", "-c" 长选项 + 短选项可同参
default any None 未提供时的值 required=True 互斥
type Click 类型 推断 值解析与校验 click.INTChoice
required bool False 是否必填 无 default 且非 flag 时可设
is_flag bool False 布尔开关,无值 --verbose 类选项
flag_value any True flag 为真时的值 少见
multiple bool False 可重复出现 收集多个 --file a --file b
count bool False 计数出现次数 -v/-vv/-vvv verbosity
prompt bool/str False 交互式输入 缺省时提示;str 为提示语
hide_input bool False 不回显输入 配合 password
help str 选项说明 --help 中展示
show_default bool 自动 帮助中显示默认值 建议对非 obvious default 开启

示例:类型与 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import click

@click.command()
@click.option("--count", "-c", default=1, type=click.INT, help="重复次数")
@click.option("--verbose", "-v", is_flag=True, help="详细输出")
def greet(count, verbose):
for _ in range(count):
msg = "Hi"
if verbose:
msg += " (verbose)"
click.echo(msg)

if __name__ == "__main__":
greet()
1
python greet.py -c 2 -v

示例:Choice 与 prompt

1
2
3
4
5
6
7
8
9
10
@click.command()
@click.option(
"--format",
type=click.Choice(["json", "yaml"], case_sensitive=False),
default="json",
show_default=True,
)
@click.option("--name", prompt=True, help="你的名字")
def run(format, name):
click.echo(f"{name} -> {format}")

示例:multiple 与 count

1
2
3
4
5
@click.command()
@click.option("--path", multiple=True, type=click.Path(exists=True))
@click.option("-v", count=True)
def process(path, v):
click.echo(f"verbosity={v}, paths={path}")

5. @click.argument

为命令添加位置参数(按顺序出现在命令行末尾)。

重点参数

参数 类型 默认值 作用 配置建议
名称 str 必填 Python 参数名 "filename"
type Click 类型 STRING 解析类型 路径用 click.Path()
nargs int 1 消耗参数个数 nargs=-1 贪婪收集剩余
required bool True 是否必须 有 default 时可 False

示例

1
2
3
4
5
6
7
import click

@click.command()
@click.argument("src", type=click.Path(exists=True))
@click.argument("dst", type=click.Path())
def copy(src, dst):
click.echo(f"copy {src} -> {dst}")
1
python copy_cli.py input.txt output.txt

nargs=-1 收集剩余参数

1
2
3
4
5
@click.command()
@click.argument("files", nargs=-1, type=click.Path(exists=True))
def cat(files):
for f in files:
click.echo(open(f).read())

6. @click.pass_context

将 Click Context 对象注入为第一个参数 ctx,用于读写上下文、调用其他命令。

重点参数

参数 类型 默认值 作用 配置建议
无参装饰器 被装饰函数首参必须为 ctx

Context 常用成员

成员 作用
ctx.obj 用户自定义对象,group 间传递
ctx.params 当前命令参数字典
ctx.invoke(other_cmd, **kwargs) 调用同组其他命令
ctx.exit(code) 以指定退出码结束

示例:group 共享配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import click

@click.group()
@click.option("--debug/--no-debug", default=False)
@click.pass_context
def cli(ctx, debug):
ctx.ensure_object(dict)
ctx.obj["DEBUG"] = debug

@cli.command()
@click.pass_context
def build(ctx):
if ctx.obj["DEBUG"]:
click.echo("DEBUG mode")
click.echo("building...")

if __name__ == "__main__":
cli()
1
python ctx_cli.py --debug build

示例:invoke 其他命令

1
2
3
4
5
@cli.command()
@click.pass_context
def all(ctx):
ctx.invoke(hello, name="World")
ctx.invoke(version)

7. @click.pass_obj

注入 ctx.obj(须先在 group 用 ensure_object 初始化),省略每次写 ctx.obj

1
2
3
4
5
6
7
8
9
10
@click.group()
@click.pass_context
def cli(ctx):
ctx.ensure_object(dict)
ctx.obj["USER"] = "admin"

@cli.command()
@click.pass_obj
def whoami(obj):
click.echo(obj["USER"])

8. @click.version_option

自动添加 --version 选项。

参数 类型 默认值 作用 配置建议
version str 包版本 显示字符串 importlib.metadata.version("pkg")
prog_name str 自动 显示的程序名
message str "%(prog)s, version %(version)s" 格式
package_name str 从包读版本 version 二选一
1
2
3
4
@click.command()
@click.version_option(version="1.0.0")
def main():
click.echo("run")

9. @click.confirmation_option

执行前要求确认(如 --yes 跳过)。

参数 类型 默认值 作用 配置建议
prompt str "Do you want to continue?" 确认提示 destructive 操作必改文案
abort bool True 用户否时是否 abort
yes str "yes" 视为确认的输入
no str "no" 视为拒绝的输入
1
2
3
4
@click.command()
@click.confirmation_option(prompt="确定删除全部数据?")
def wipe():
click.echo("wiped")

10. @click.password_option

隐藏输入的密码选项(等价于 hide_input=True 的 option)。

参数 类型 默认值 作用 配置建议
名称 str 默认 --password 选项名 "--token"
prompt str "Password" 缺省时的提示
confirmation_prompt bool False 二次确认 注册/改密时开启
1
2
3
4
@click.command()
@click.password_option(confirmation_prompt=True)
def login(password):
click.echo(f"len={len(password)}")

11. 常用 Click 类型(配合 option/argument)

类型 用途 示例
click.INT / FLOAT / STRING 基础类型 type=click.INT
click.BOOL 布尔 更常用 is_flag=True
click.Choice([...]) 枚举 见 §4
click.Path(exists=True, dir_okay=False) 路径校验 输入文件
click.File("r") 打开文件 自动 close
click.DateTime(formats=[...]) 日期时间
click.IntRange(min=0, max=100) 整数范围
click.FloatRange 浮点范围
1
2
3
4
5
@click.command()
@click.option("--port", type=click.IntRange(1, 65535), default=8080)
@click.option("--config", type=click.File("r"))
def serve(port, config):
click.echo(f"port={port}, config={config.name}")

12. 链式命令(chain=True)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@click.group(chain=True)
def pipeline():
pass

@pipeline.command()
@click.argument("path", type=click.Path(exists=True))
def read(path):
click.echo(f"read {path}")

@pipeline.command()
def transform():
click.echo("transform")

if __name__ == "__main__":
pipeline()
1
python chain_cli.py read data.txt transform

13. 完整综合示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import click

@click.group(invoke_without_command=True)
@click.option("--verbose", "-v", count=True, help="日志详细程度")
@click.pass_context
def cli(ctx, verbose):
"""文件小工具"""
ctx.ensure_object(dict)
ctx.obj["VERBOSE"] = verbose
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())

@cli.command()
@click.argument("path", type=click.Path(exists=True))
@click.pass_obj
def stat(obj, path):
import os
size = os.path.getsize(path)
if obj["VERBOSE"]:
click.echo(f"[verbose] path={path}")
click.echo(size)

@cli.command()
@click.option("--force", is_flag=True, help="跳过确认")
@click.confirmation_option(prompt="确认写入?")
def init(force):
click.echo("initialized")

if __name__ == "__main__":
cli()

14. 适用 / 不适用

适用

  • 需要子命令、帮助自动生成、类型校验的 CLI 工具
  • 脚本升级为可分发命令(配合 entry_points

不适用

  • 极简单次脚本且参数极少 → argparse 零依赖足够
  • 复杂 TUI 全屏交互 → 考虑 textual / prompt_toolkit

15. 易踩坑

  • 装饰器顺序@click.command() 应紧贴 def@click.option 在其上方
  • 函数参数名必须与 option/argument 声明的名称一致
  • is_flag=True 时不应再设 type=click.BOOL 除非明确需要
  • nargs=-1 的 argument 通常放最后;与 option 混用时注意顺序
  • click.echo 代替 print,兼容 unicode 与 Windows 控制台
  • 测试时用 CliRunnerfrom click.testing import CliRunner
  • 布尔选项 --foo/--no-foo 比单个 flag 更适合需要显式关闭的默认值场景

CliRunner 测试片段

1
2
3
4
5
6
7
8
from click.testing import CliRunner
from myapp import hello

def test_hello():
runner = CliRunner()
result = runner.invoke(hello, ["World"])
assert result.exit_code == 0
assert "Hello, World" in result.output

16. 近邻替代

替代 何时用
argparse 标准库、无子命令依赖
typer 基于类型注解的 CLI,底层仍可用 Click
fire 快速暴露模块/对象为 CLI

17. 参考

-------------本文结束感谢您的阅读-------------