fire 是 Google 出的命令行生成库,思路和 click 那一类完全相反:不写装饰器,不声明参数,把普通函数交给它,函数签名自动变成命令行。

写过 click 的人都知道,一个带两个子命令的小工具,光参数声明就要占十几行。fire 的卖点是这段声明代码可以全省掉。这篇验证两件事:省下来的代码值不值,代价是什么。

三个函数就是三个命令

import fire

def hello(name, times=1, loud=False):
    """打招呼"""
    msg = "你好 " + name
    if loud: msg = msg.upper() + "!"
    for _ in range(times): print(msg)

def add(a, b):
    """两数相加"""
    print(a + b)

if __name__ == "__main__":
    fire.Fire({"hello": hello, "add": add})

就这些。没有 @click.command(),没有 add_argument,也没有类型注解。传给 fire.Fire 一个字典,字典的键就是子命令名。

和 click、typer 比行数与启动

同样两个子命令(hello 带一个位置参数加两个选项,add 带两个整数),三份实现都跑 --help,各 5 次取中位:

实现有效代码行数--help 耗时
fire12 行1326 毫秒
click20 行979 毫秒
typer14 行1422 毫秒

行数确实最少,但启动时间不是最优:比 click 慢了 347 毫秒。fire 启动时要反射函数的签名、构建命令树、再拼帮助文本,这套动作在纯 Python 里做,省不掉。

实际调用也一样,参数形态的自动转换倒是正常,两个字符串参数被正确当成整数算出了 7:

调用耗时输出
hello 实测库 --times=21243 毫秒两行「你好 实测库」
add 3 41170 毫秒7
--help(顶层)1326 毫秒stdout 0 字节 / stderr 209 字节
hello --help(单命令)未单独计时stdout 0 字节 / stderr 355 字节

第一个坑:帮助信息写到 stderr

这是实测里最意外的一处。执行 python cli_fire.py --help,stdout 是 0 字节,209 字节全在 stderr:

INFO: Showing help with the command 'cli_fire.py -- --help'.

NAME
    cli_fire.py

SYNOPSIS
    cli_fire.py COMMAND

COMMANDS
    COMMAND is one of the following:

     hello
     add

后果很具体:fire ... --help | less 看到的是空白,fire ... --help > help.txt 拿到的是空文件。要接管道得写成 2>&1。文档里那个 -- --help 的写法,是它在提示你真正的帮助命令是加两个减号分隔符。

第二个坑:参数传错直接抛 traceback

把 --times 传成字符串,期待它报「参数类型不对」再给一段用法。实际输出是完整的 Python 异常栈:

  File "cli_fire.py", line 8, in hello
    for _ in range(times): print(msg)
             ~~~~~^^^^^^^
TypeError: 'str' object cannot be interpreted as an integer

退出码 1,没有任何用法提示。原因是 fire 把字符串原样传进了函数,类型检查发生在函数体里面,它根本插不上手。click 遇到这种情况会打印 Error: Invalid value for '--times' 再加一段 usage,用户知道该怎么改。

两种情况的差别在于:你的函数自己抛异常时,用户看到的是业务错误还是用法错误,全靠函数写得好不好。

第三个坑:没有 --version

$ python cli_fire.py --version
ERROR: Cannot find key: --version
Usage: cli_fire.py <command>
  available commands:
    hello
    add
exit code: 2

它把 --version 当成一个不存在的子命令。要给工具加版本号,得自己定义一个 version() 函数塞进字典。

子命令的帮助也只能自己看

单看一个子命令同样别扭。python cli_fire.py hello --help 的 stdout 仍是 0 字节,355 字节全在 stderr:

INFO: Showing help with the command 'cli_fire.py hello -- --help'.

NAME
    cli_fire.py hello - 打招呼

SYNOPSIS
    cli_fire.py hello NAME <flags>

POSITIONAL ARGUMENTS
    NAME

FLAGS
    --times
    --loud

内容本身是对的:位置参数和两个开关都列出来了,docstring 也被当成描述抓了过去。问题只在于流向,脚本里想把它当文本处理,必须显式合并 stderr。

什么时候用它

内部脚本、一次性工具。同事要跑一个数据处理脚本,参数就是几个文件名和开关,写 12 行比写 20 行省事,帮助面板难看也无所谓。

已经有现成的类和函数,想直接暴露成命令。fire 能接对象、模块、字典,不需要改原有代码结构,这点 click 做不到。

对外发布的工具不要用。帮助信息进 stderr、参数错误抛 traceback、没有版本号,这三条在外发场景里都是硬伤,用户会当成程序崩了。

没测的部分列一下,3 项:嵌套子命令(多级字典)的行为 / 从类实例暴露方法的可用性 / 与 argparse 的互操作。