简介

Click 是 Python 中用于构建命令行界面的强大库,设计目标是:用尽可能少的代码,构建可组合的命令行界面

入门

import click

@click.command()
@click.option('--count', default=1, help='重复次数')
@click.argument('name')
def hello(count, name):
    for _ in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    hello()

运行

$ python app.py --count=3 Alice
Hello, Alice!
Hello, Alice!
Hello, Alice!

核心组件

注册命令

@click.command()

位置参数

@click.argument('filename')
调用时必须写
$ python app.py myfile.txt

可选参数

@click.option('--debug', is_flag=True, help='开启调试模式')
@click.option('--level', type=int, default=3)

输出函数

click.echo()

类型系统

@click.argument('path', type=click.Path(exists=True))

创建命令组

@click.group()
def cli():
    pass

@cli.command()
def init():
    click.echo("初始化")

@cli.command()
def start():
    click.echo("启动")

if __name__ == '__main__':
    cli()

执行

$ python app.py init
初始化

提示

@click.option('--username', prompt='用户名')
@click.option('--password', prompt=True, hide_input=True)

回调函数

在参数解析后执行特定函数

def validate(ctx, param, value):
    if value < 0:
        raise click.BadParameter('必须为非负数')
    return value

@click.option('--num', callback=validate, type=int)

命令别名

@click.command(name='run', help='执行')

自定义帮助输出

@click.group(context_settings=dict(help_option_names=['-h', '--help']))

实用场景

多值选项

@click.option('--path', multiple=True)

调用

$ python app.py --path a --path b

布尔值选项

@click.option('--verbose/--no-verbose', default=True)

环境变量选项

@click.option('--config', envvar='APP_CONFIG')

实战项目结构

下面是一个实战级的 Python CLI 工具项目示例,基于 Click 实现,模拟一个日常常见的命令行工具

项目目标

我们创建一个名为 tasker 的命令行工具,用来:

  • 管理待办事项(任务列表)
  • 支持子命令:add, list, done, remove
  • 本地保存任务(使用 JSON 文件作为数据库)
  • 可选支持 --verbose 模式

项目结构

tasker/
├── __main__.py      # 入口点
├── cli.py           # 命令组定义
├── commands/
│   ├── add.py       # 添加任务
│   ├── list.py      # 列出任务
│   ├── done.py      # 标记完成
│   └── remove.py    # 删除任务
├── storage.py       # JSON 文件存储逻辑
├── model.py         # 任务数据结构
├── config.py        # 配置管理(如路径)
└── task_db.json     # 数据文件(运行时自动生成)

安装入口(可选)

pyproject.tomlsetup.py 中添加:

[project.scripts]
tasker = "tasker.__main__:cli"

代码示例

__main__.py

from tasker.cli import cli

if __name__ == "__main__":
    cli()

cli.py

import click
from tasker.commands import add, list, done, remove

@click.group()
@click.option('--verbose', is_flag=True, help="显示详细信息")
@click.pass_context
def cli(ctx, verbose):
    ctx.ensure_object(dict)
    ctx.obj['verbose'] = verbose

cli.add_command(add.command)
cli.add_command(list.command)
cli.add_command(done.command)
cli.add_command(remove.command)

commands/add.py

import click
from tasker.storage import load_tasks, save_tasks

@click.command()
@click.argument('title')
@click.pass_context
def command(ctx, title):
    tasks = load_tasks()
    tasks.append({'title': title, 'done': False})
    save_tasks(tasks)
    click.echo(f"✅ 添加任务: {title}")

commands/list.py

import click
from tasker.storage import load_tasks

@click.command()
@click.pass_context
def command(ctx):
    tasks = load_tasks()
    for i, task in enumerate(tasks, 1):
        status = "✔" if task['done'] else "✘"
        click.echo(f"{i}. [{status}] {task['title']}")

commands/done.py

import click
from tasker.storage import load_tasks, save_tasks

@click.command()
@click.argument('index', type=int)
@click.pass_context
def command(ctx, index):
    tasks = load_tasks()
    if 0 < index <= len(tasks):
        tasks[index - 1]['done'] = True
        save_tasks(tasks)
        click.echo(f"🎉 任务完成: {tasks[index - 1]['title']}")
    else:
        click.echo("❌ 无效的任务编号")

commands/remove.py

import click
from tasker.storage import load_tasks, save_tasks

@click.command()
@click.argument('index', type=int)
@click.pass_context
def command(ctx, index):
    tasks = load_tasks()
    if 0 < index <= len(tasks):
        removed = tasks.pop(index - 1)
        save_tasks(tasks)
        click.echo(f"🗑 删除任务: {removed['title']}")
    else:
        click.echo("❌ 无效的任务编号")

storage.py

import os
import json

DB_PATH = os.path.expanduser("~/.tasker_db.json")

def load_tasks():
    if not os.path.exists(DB_PATH):
        return []
    with open(DB_PATH, "r") as f:
        return json.load(f)

def save_tasks(tasks):
    with open(DB_PATH, "w") as f:
        json.dump(tasks, f, indent=2)

示例运行效果

$ tasker add "写 Click 教程"
✅ 添加任务: 写 Click 教程

$ tasker list
1. [✘] 写 Click 教程

$ tasker done 1
🎉 任务完成: 写 Click 教程

$ tasker list
1. [✔] 写 Click 教程

$ tasker remove 1
🗑 删除任务: 写 Click 教程