<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>CLI on Zata-砸它</title><link>https://www.zata.cc/tags/cli/</link><description>Recent content in CLI on Zata-砸它</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Example Person</copyright><lastBuildDate>Thu, 11 Jun 2026 22:50:27 +0800</lastBuildDate><atom:link href="https://www.zata.cc/tags/cli/index.xml" rel="self" type="application/rss+xml"/><item><title>Typer + Rich 入门教程</title><link>https://www.zata.cc/p/typer%E5%92%8Crich%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/index.md/</link><pubDate>Tue, 09 Jun 2026 15:30:00 +0800</pubDate><guid>https://www.zata.cc/p/typer%E5%92%8Crich%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/index.md/</guid><description>&lt;img src="https://www.zata.cc/p/typer%E5%92%8Crich%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/index.md/images/index/index.png" alt="Featured image of post Typer + Rich 入门教程" />&lt;p>最近在写一个运维工具,翻了一圈 Python 写 CLI 的库,最后选了 &lt;strong>Typer + Rich&lt;/strong>。这套组合的好处是:用类型注解写 CLI,自动拿到漂亮的 help 页面、参数解析、子命令,基本不用自己处理模板代码。这篇把学习过程整理一下,适合第一次用 Typer 的同学。&lt;/p>
&lt;hr>
&lt;h2 id="目录">目录
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e6%98%af-typer--rich" >为什么是 Typer + Rich&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e4%ba%94%e5%88%86%e9%92%9f%e4%b8%8a%e6%89%8b" >五分钟上手&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e6%a0%b8%e5%bf%83%e6%a6%82%e5%bf%b5" >核心概念&lt;/a>
&lt;ul>
&lt;li>&lt;a class="link" href="#arguments%e5%bf%85%e5%a1%ab%e4%bd%8d%e7%bd%ae%e5%8f%82%e6%95%b0" >Arguments:必填位置参数&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#options%e5%8f%af%e9%80%89%e5%91%bd%e5%90%8d%e5%8f%82%e6%95%b0" >Options:可选命名参数&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e9%94%99%e8%af%af%e5%a4%84%e7%90%86" >错误处理&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a class="link" href="#%e5%ad%90%e5%91%bd%e4%bb%a4%e5%b5%8c%e5%a5%97-typer-%e5%ba%94%e7%94%a8" >子命令:嵌套 Typer 应用&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#rich-%e6%80%8e%e4%b9%88%e7%94%a8" >Rich 怎么用&lt;/a>
&lt;ul>
&lt;li>&lt;a class="link" href="#%e8%87%aa%e5%8a%a8%e9%83%a8%e5%88%86%e9%9b%b6%e9%85%8d%e7%bd%ae" >自动部分(零配置)&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e6%89%8b%e5%8a%a8%e9%83%a8%e5%88%86%e7%94%bb%e8%a1%a8%e6%a0%bc" >手动部分:画表格&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e8%bf%9b%e5%ba%a6%e6%9d%a1--json--markdown" >进度条 / JSON / Markdown&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a class="link" href="#%e5%ae%9e%e6%88%98%e9%87%8c%e5%b8%b8%e7%94%a8%e7%9a%84%e5%87%a0%e4%b8%aa%e6%a8%a1%e5%bc%8f" >实战里常用的几个模式&lt;/a>
&lt;ul>
&lt;li>&lt;a class="link" href="#eager---version" >eager &lt;code>--version&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e9%85%8d%e7%bd%ae%e9%bb%98%e8%ae%a4%e5%80%bc--flag-%e8%a6%86%e7%9b%96" >配置默认值 + flag 覆盖&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#-dry-run-%e6%a8%a1%e5%bc%8f" >&lt;code>--dry-run&lt;/code> 模式&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a class="link" href="#%e6%8e%92%e9%94%99%e5%b0%8f%e6%8a%84" >排错小抄&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e4%b8%80%e9%a1%b5%e9%80%9f%e6%9f%a5%e4%bb%a3%e7%a0%81" >一页速查代码&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e7%bb%83%e4%b9%a0%e9%a2%98" >练习题&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" >参考资料&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="为什么是-typer--rich">为什么是 Typer + Rich
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Typer&lt;/strong>:基于 Click,用 Python &lt;strong>类型注解&lt;/strong>构建 CLI。help 文本、参数解析、子命令、shell completion 都是开箱即用。&lt;/li>
&lt;li>&lt;strong>Rich&lt;/strong>:终端美化库。Typer 内置 Rich 集成,所以你看到的 &lt;code>╭─╮&lt;/code> 框线、分组的 Options/Commands 面板、错误高亮全是 Rich 自动渲染的,不用自己写一行 Rich 代码。Rich 自己还能画表格、进度条、Markdown、JSON 语法高亮等。&lt;/li>
&lt;/ul>
&lt;p>两个库配合,Python 写 CLI 的体验接近 &lt;code>cargo&lt;/code> / &lt;code>npm&lt;/code> 这种专业工具。&lt;/p>
&lt;hr>
&lt;h2 id="五分钟上手">五分钟上手
&lt;/h2>&lt;p>最小可运行的 CLI,五行就够:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Typer&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">hello&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Say hello to NAME.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hello, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">app&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>保存为 &lt;code>demo.py&lt;/code>,运行 &lt;code>python demo.py Alice&lt;/code> → &lt;code>Hello, Alice!&lt;/code>。
加 &lt;code>--help&lt;/code> 就能看到 Rich 渲染的 help 页面,&lt;strong>零额外配置&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="核心概念">核心概念
&lt;/h2>&lt;h3 id="arguments必填位置参数">Arguments:必填位置参数
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">deploy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Deploy VERSION to ENV.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>调用:&lt;code>deploy prod v1.2.0&lt;/code>。少传任何一个,Typer 都会报错并提示。&lt;/p>
&lt;h3 id="options可选命名参数">Options:可选命名参数
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">deploy&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">env&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">version&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">region&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;us-east-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;AWS region.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dry_run&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--dry-run&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Preview only.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">verbose&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-v&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Verbosity level.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>调用:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">deploy prod v1.2.0 --region eu-west-1 --dry-run -vvv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>几个关键点:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>写法&lt;/th>
&lt;th>含义&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>param: str&lt;/code>&lt;/td>
&lt;td>必填位置参数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>param: str = typer.Option(&amp;quot;default&amp;quot;)&lt;/code>&lt;/td>
&lt;td>有默认值的 option&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>param: bool = typer.Option(False, &amp;quot;--dry-run&amp;quot;)&lt;/code>&lt;/td>
&lt;td>flag,出现即 True&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>param: int = typer.Option(0, &amp;quot;-v&amp;quot;, count=True)&lt;/code>&lt;/td>
&lt;td>短选项,支持 &lt;code>-v -v -v&lt;/code> 累加&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>typer.Option(..., help=&amp;quot;...&amp;quot;)&lt;/code>&lt;/td>
&lt;td>参数的 help 文本&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>函数 docstring 第一段&lt;/td>
&lt;td>命令的简介&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>类型注解决定一切&lt;/strong> — &lt;code>str&lt;/code> 收字符串,&lt;code>int&lt;/code> 收整数,&lt;code>bool&lt;/code> 自动变 flag,&lt;code>Path&lt;/code> 自动转 &lt;code>pathlib.Path&lt;/code>,&lt;code>Literal[&amp;quot;a&amp;quot;,&amp;quot;b&amp;quot;]&lt;/code> 限制枚举。&lt;/p>
&lt;h3 id="错误处理">错误处理
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">deploy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">env&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;staging&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;prod&amp;#34;&lt;/span>&lt;span class="p">}:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">BadParameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;unknown env: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">env&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>typer.BadParameter&lt;/code> 和 &lt;code>typer.Exit(code=1)&lt;/code> 是 Typer 的标准错误出口,会自动用红色打印并以非零码退出。&lt;/p>
&lt;hr>
&lt;h2 id="子命令嵌套-typer-应用">子命令:嵌套 Typer 应用
&lt;/h2>&lt;p>复杂 CLI 一般会拆成多个子命令,像 &lt;code>git&lt;/code> 那样:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">mycli db backup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mycli db list
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mycli env provision
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mycli logs tail
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>实现方式是 &lt;strong>每个子域一个 Typer 应用,再挂到主应用上&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># mycli/cli.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">mycli.db&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cli&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">db_cli&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">mycli.env&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cli&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">env_cli&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">mycli.logs&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cli&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">logs_cli&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Typer&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;mycli&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">no_args_is_help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 不带子命令时显示 help&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">add_completion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 关闭自动安装 shell completion 的提示&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_typer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db_cli&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;db&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Database operations.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_typer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">env_cli&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;env&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Environment provisioning.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_typer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logs_cli&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;logs&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Log utilities.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># mycli/db/cli.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Typer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">no_args_is_help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">add_completion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;backup&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">backup_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Back up the database.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;list&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">list_command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;List recent backup runs.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>模式要点:&lt;/p>
&lt;ul>
&lt;li>子命令文件独立,各自定义一个 &lt;code>app = typer.Typer(...)&lt;/code>。&lt;/li>
&lt;li>&lt;code>add_typer(..., name=&amp;quot;db&amp;quot;)&lt;/code> 决定子命令的拼写。&lt;/li>
&lt;li>&lt;code>add_completion=False&lt;/code> 在子应用里也建议加上,避免每个子命令都重复提示装 completion。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="rich-怎么用">Rich 怎么用
&lt;/h2>&lt;h3 id="自动部分零配置">自动部分(零配置)
&lt;/h3>&lt;p>&lt;code>--help&lt;/code>、参数错误、未识别命令 — Typer 自动用 Rich 渲染,免费拿到好看的 help 和红字报错。&lt;/p>
&lt;h3 id="手动部分画表格">手动部分:画表格
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.console&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Console&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.table&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Table&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">console&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">status&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Show service status.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Table&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Service Status&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;cyan&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;State&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;green&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_column&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Uptime&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">justify&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;right&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_row&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;api&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;running&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;12d 4h&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">table&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_row&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;worker&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;stopped&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;—&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">table&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>效果:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"> Service Status
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┏━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┃ Name ┃ State ┃ Uptime ┃
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┡━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━┩
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ api │ running │ 12d 4h │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ worker │ stopped │ — │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────┴───────────┴─────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>column&lt;/code> 的 &lt;code>style=&lt;/code> 可以指定颜色,&lt;code>justify=&amp;quot;right&amp;quot;&lt;/code> 控制对齐,&lt;code>add_row&lt;/code> 一行行加数据。&lt;/p>
&lt;h3 id="进度条--json--markdown">进度条 / JSON / Markdown
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.progress&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">track&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.json&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">JSON&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.markdown&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Markdown&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">track&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Processing...&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">do_work&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">JSON&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;{&amp;#34;a&amp;#34;: 1}&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Markdown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;# title&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">*body*&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="实战里常用的几个模式">实战里常用的几个模式
&lt;/h2>&lt;h3 id="eager---version">eager &lt;code>--version&lt;/code>
&lt;/h3>&lt;p>&lt;code>--version&lt;/code> 需要在子命令参数解析前就生效,否则 &lt;code>mycli --version db backup&lt;/code> 会被当成&amp;quot;先解析 db backup,再回头看 &amp;ndash;version&amp;quot;。&lt;/p>
&lt;p>写法是 &lt;code>is_eager=True&lt;/code> + callback 抛 &lt;code>typer.Exit()&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_print_version_and_exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">echo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;mycli &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">__version__&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Exit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.callback&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_main&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">version&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--version&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-V&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">is_eager&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">callback&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">_print_version_and_exit&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Show version and exit.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="配置默认值--flag-覆盖">配置默认值 + flag 覆盖
&lt;/h3>&lt;p>CLI 工具经常要从 &lt;code>.env&lt;/code> 读配置,又要允许 flag 显式覆盖。最简单的写法是用一个 &lt;code>_resolve&lt;/code> 辅助函数:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_resolve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">default&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">default&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后每个 Option 都写成 &lt;code>Optional[str] = typer.Option(None, ...)&lt;/code>,命令体里统一用 &lt;code>_resolve(cli_flag, settings.field)&lt;/code> 决定最终值:&lt;/p>
&lt;ul>
&lt;li>没传 flag → 用 &lt;code>.env&lt;/code> 里的配置&lt;/li>
&lt;li>传了 flag → 覆盖配置&lt;/li>
&lt;li>&lt;code>.env&lt;/code> 也没设 → 用代码里的 default&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>坑提醒&lt;/strong>:别把 &lt;code>default=settings.field&lt;/code> 写到 Option 上 — Python 的默认参数是在 import 时求值的,不在运行命令的工作目录,容易踩到。&lt;/p>
&lt;/blockquote>
&lt;h3 id="--dry-run-模式">&lt;code>--dry-run&lt;/code> 模式
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">dry_run&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--dry-run&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dry_run&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">dry_run&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;[yellow]DRY RUN[/yellow] — no network calls&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plan&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">do_real_work&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plan&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>CI / smoke test 里非常有用 — &lt;code>--dry-run&lt;/code> 不打网络也能验证命令链路。&lt;/p>
&lt;hr>
&lt;h2 id="排错小抄">排错小抄
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>现象&lt;/th>
&lt;th>原因 / 修法&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>--help&lt;/code> 显示一坨不分组&lt;/td>
&lt;td>老版本 Typer;升级到 0.12+ 并装 Rich&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>中文 help 乱码&lt;/td>
&lt;td>终端编码不是 UTF-8;&lt;code>export LANG=en_US.UTF-8&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>typer.Option(None, ...)&lt;/code> 拿不到 None&lt;/td>
&lt;td>漏 &lt;code>from __future__ import annotations&lt;/code> 或没标注 &lt;code>Optional&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>子命令 help 不显示&lt;/td>
&lt;td>子 &lt;code>Typer()&lt;/code> 没设 &lt;code>no_args_is_help=True&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>--version&lt;/code> 在子命令里被吞&lt;/td>
&lt;td>缺 &lt;code>is_eager=True&lt;/code> + &lt;code>callback&lt;/code> 抛 &lt;code>typer.Exit()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>default 用配置项不生效&lt;/td>
&lt;td>Python 默认参数在 import 时求值,改用 &lt;code>_resolve&lt;/code> 模式&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="一页速查代码">一页速查代码
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">typer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">rich.console&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Console&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Typer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">no_args_is_help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">add_completion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">console&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@app.command&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">greet&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 必填位置参数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">greeting&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Hi&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-g&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="c1"># 可选 option&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">repeat&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="c1"># 数字&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loud&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">typer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Option&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--loud&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="c1"># flag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Say GREETING to NAME.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">greeting&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">loud&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">msg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">upper&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">console&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">repeat&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">app&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="练习题">练习题
&lt;/h2>&lt;p>学完上面这些,推荐按顺序做四道题,做完基本就掌握了:&lt;/p>
&lt;ol>
&lt;li>写一个 &lt;code>notes add &amp;lt;text&amp;gt;&lt;/code> / &lt;code>notes list&lt;/code> 的小型 CLI(用 JSON 文件存)。&lt;/li>
&lt;li>给 &lt;code>notes list&lt;/code> 加一个 &lt;code>--tag&lt;/code> 过滤 option,类型用 &lt;code>Optional[str]&lt;/code>。&lt;/li>
&lt;li>用 &lt;code>rich.table.Table&lt;/code> 把 &lt;code>notes list&lt;/code> 渲染成表格。&lt;/li>
&lt;li>加一个 &lt;code>--export json|csv&lt;/code> option,用 &lt;code>rich.json.JSON&lt;/code> 或自己拼 CSV。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="参考资料">参考资料
&lt;/h2>&lt;ul>
&lt;li>Typer 官方文档: &lt;a class="link" href="https://typer.tiangolo.com/" target="_blank" rel="noopener"
>https://typer.tiangolo.com/&lt;/a>&lt;/li>
&lt;li>Rich 官方文档: &lt;a class="link" href="https://rich.readthedocs.io/" target="_blank" rel="noopener"
>https://rich.readthedocs.io/&lt;/a>&lt;/li>
&lt;li>实战参考:可以找一个真实的 Python CLI 项目翻一翻,比如 &lt;code>pipx&lt;/code>、&lt;code>httpie&lt;/code>、&lt;code>poetry&lt;/code> 这些开源工具的源码,看它们怎么组织子命令、怎么用 Rich 画表格,比自己闭门造车快得多。&lt;/li>
&lt;/ul></description></item></channel></rss>