<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://shasnow.top/</id>
  <title type="text">雪落无影</title>
  <subtitle type="text">分享</subtitle>
  <updated>2026-09-22T00:00:00.000Z</updated>
  <author><name>Shasnow</name></author>
  <link rel="alternate" href="https://shasnow.top/"/>
  <link rel="self" href="https://shasnow.top/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/</id>
      <title type="text">Python cmd2：功能总览</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/"/>
      <summary type="text">cmd2 全部功能文档的总览索引，汇集参数处理、命令、补全、历史、钩子、主题等专题文章的入口链接。</summary>
      <content type="html"><![CDATA[<section><h1>功能总览<a href="#功能总览"><span>#</span></a></h1><ul>
<li><a href="argument_processing/">参数处理</a></li>
<li><a href="annotated/">注解式参数处理</a></li>
<li><a href="async_builtin_clipboard/">异步命令</a></li>
<li><a href="async_builtin_clipboard/">内置命令</a></li>
<li><a href="async_builtin_clipboard/">剪贴板集成</a></li>
<li><a href="command_completion_disable/">命令</a></li>
<li><a href="command_completion_disable/">补全</a></li>
<li><a href="command_completion_disable/">禁用命令</a></li>
<li><a href="embedded_output_help/">嵌入式 Python Shell</a></li>
<li><a href="embedded_output_help/">生成输出</a></li>
<li><a href="embedded_output_help/">帮助</a></li>
<li><a href="history/">历史记录</a></li>
<li><a href="hooks/">钩子</a></li>
<li><a href="app_setup/">初始化</a></li>
<li><a href="misc_packaging/">杂项功能</a></li>
<li><a href="modular_commands/">模块化命令</a></li>
<li><a href="app_setup/">多行命令</a></li>
<li><a href="os/">与操作系统集成</a></li>
<li><a href="misc_packaging/">打包分发 cmd2 应用</a></li>
<li><a href="settings_plugins/">插件</a></li>
<li><a href="prompt_redirection/">提示符</a></li>
<li><a href="prompt_redirection/">输出重定向与管道</a></li>
<li><a href="scripting/">脚本</a></li>
<li><a href="settings_plugins/">设置</a></li>
<li><a href="shortcuts_aliases_macros/">快捷方式、别名和宏</a></li>
<li><a href="app_setup/">启动命令</a></li>
<li><a href="table_creation/">表格创建</a></li>
<li><a href="theme/">主题</a></li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/annotated/</id>
      <title type="text">Python cmd2：注解式参数处理</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/annotated/"/>
      <summary type="text">介绍实验性的 @with_annotated 装饰器，如何用类型注解自动构建 argparse 解析器、注解到 argparse 的映射规则、元数据类与子命令。</summary>
      <content type="html"><![CDATA[<section><h1>注解式参数处理<a href="#注解式参数处理"><span>#</span></a></h1><p>:::warning 实验性功能</p><p><code>@with_annotated</code> 装饰器及其配套的 <code>Argument</code> / <code>Option</code> 元数据类是<strong>实验性</strong>的。其公开 API、可接受的类型注解范围以及生成的 argparse 行为都可能在未来的版本中发生变化，且不提供弃用周期。如果你依赖当前的确切语义，请锁定特定的 <code>cmd2</code> 版本，并在升级时重新审视你的用法。</p><p>对于需要稳定行为的生产代码，请改用 <a href="../argument_processing/#with_argparser-%E8%A3%85%E9%A5%B0%E5%99%A8">@with_argparser</a>。</p><p>:::</p><p>[@with_annotated][cmd2.with_annotated] 装饰器会根据被装饰函数的类型注解自动构建 argparse 解析器。无需手动调用 <code>add_argument()</code>，命令函数体直接接收带类型的关键字参数，而不是一个 <code>argparse.Namespace</code>。</p><p>两个装饰器可以互换使用——下面是同一个命令的两种写法：</p><p><strong>使用 <code>@with_annotated</code>：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>count</span></span><span>:</span><span> </span><span>int</span><span> </span><span>=</span><span><span> </span><span>1</span></span><span>,</span><span><span> </span><span>loud</span></span><span>:</span><span> </span><span>bool</span><span> </span><span>=</span><span><span> </span><span>False</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(count):</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>msg </span><span>=</span><span> </span><span>f</span><span>"Hello </span><span>{</span><span>name</span><span>}</span><span>"</span></div></div><div><div><div>5</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(msg.</span><span>upper</span><span>() </span></span><span>if</span><span> loud </span><span>else</span><span> msg)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>使用 <code>@with_argparser</code>：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>parser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>()</span></span></div></div><div><div><div>2</div></div><div><span><span>parser.</span><span>add_argument</span><span>(</span></span><span>"name"</span><span>, </span><span>help</span><span>=</span><span>"person to greet"</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>parser.</span><span>add_argument</span><span>(</span></span><span>"--count"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>1</span><span>, </span><span>help</span><span>=</span><span>"repetitions"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>parser.</span><span>add_argument</span><span>(</span></span><span>"--loud"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"shout"</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@with_argparser</span><span><span>(</span><span>parser</span><span>)</span></span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>do_greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>9</div></div><div><span>    </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(args.count):</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>msg </span><span>=</span><span> </span><span>f</span><span>"Hello </span><span>{</span><span>args.name</span><span>}</span><span>"</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(msg.</span><span>upper</span><span>() </span></span><span>if</span><span> args.loud </span><span>else</span><span> msg)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注解版本更简洁、能给你带类型的参数，并且直接支持若干高级 cmd2 特性，包括 <code>ns_provider</code>、<code>with_unknown_args</code> 和类型化子命令。当你需要一个稳定、成熟的 API 或需要对解析器进行细粒度控制时，选择 <code>@with_argparser</code>；当你想要类型注解驱动的简洁体验、并且能接受其试验性状态时，选择 <code>@with_annotated</code>。</p><section><h2>基本用法<a href="#基本用法"><span>#</span></a></h2><p>没有默认值的参数成为位置参数。有默认值的参数成为 <code>--option</code> 标志。仅限关键字的参数（在 <code>*</code> 之后）始终成为选项，若没有默认值则成为必填选项。</p><p>参数名中的下划线会在生成的标志中转换为短横线，因此 <code>dry_run</code> 会变成 <code>--dry-run</code>。你在函数体中读取的 Python 标识符仍保留下划线形式（<code>args.dry_run</code>）。若要关闭这一转换，可通过 <code>Option("--my_flag", ...)</code> 传入显式名称。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> with_annotated</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>do_greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>count</span></span><span>:</span><span> </span><span>int</span><span> </span><span>=</span><span><span> </span><span>1</span></span><span>,</span><span><span> </span><span>loud</span></span><span>:</span><span> </span><span>bool</span><span> </span><span>=</span><span><span> </span><span>False</span></span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>"""Greet someone."""</span></div></div><div><div><div>8</div></div><div><span>        </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(count):</span></div></div><div><div><div>9</div></div><div><span><span>            </span></span><span>msg </span><span>=</span><span> </span><span>f</span><span>"Hello </span><span>{</span><span>name</span><span>}</span><span>"</span></div></div><div><div><div>10</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(msg.</span><span>upper</span><span>() </span></span><span>if</span><span> loud </span><span>else</span><span> msg)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>命令 <code>greet Alice --count 3 --loud</code> 会解析出 <code>name="Alice"</code>、<code>count=3</code>、<code>loud=True</code>，并把它们作为关键字参数传入。</p></section><section><h2>注解如何映射到 argparse<a href="#注解如何映射到-argparse"><span>#</span></a></h2><p>该装饰器把 Python 类型注解转换为 <code>add_argument()</code> 调用：</p>

<table><thead><tr><th>类型注解</th><th>生成的 argparse 设置</th></tr></thead><tbody><tr><td><code>str</code></td><td>默认（无需 <code>type=</code>）</td></tr><tr><td><code>int</code>、<code>float</code></td><td><code>type=int</code> 或 <code>type=float</code></td></tr><tr><td>带默认值的 <code>bool</code></td><td>通过 <code>BooleanOptionalAction</code> 生成布尔可选标志</td></tr><tr><td>位置参数 <code>bool</code></td><td>从 <code>true/false</code>、<code>yes/no</code>、<code>on/off</code>、<code>1/0</code> 解析</td></tr><tr><td><code>Path</code></td><td><code>type=Path</code></td></tr><tr><td><code>Enum</code> 子类</td><td><code>type=converter</code>，<code>choices</code> 来自成员值</td></tr><tr><td><code>EnumA | EnumB</code>（所有成员均为 <code>Enum</code>）</td><td>逐个尝试，第一个接受该词法单元的成员胜出；<code>choices</code> 合并</td></tr><tr><td><code>decimal.Decimal</code></td><td><code>type=decimal.Decimal</code></td></tr><tr><td><code>Literal[...]</code></td><td><code>type=literal-converter</code>，<code>choices</code> 来自各值</td></tr><tr><td><code>list[T]</code> / <code>set[T]</code> / <code>frozenset[T]</code> / <code>tuple[T, ...]</code></td><td><code>nargs='+'</code>（若有默认值或是 <code>| None</code> 则为 <code>'*'</code>）</td></tr><tr><td><code>tuple[T, T]</code></td><td>固定 <code>nargs=N</code> 并使用 <code>type=T</code></td></tr><tr><td><code>T | None</code>（无默认值）</td><td>位置参数 <code>nargs='?'</code>（接受 0 或 1 个词法单元）</td></tr><tr><td><code>T | None = None</code></td><td><code>--flag</code> 选项，<code>default=None</code></td></tr></tbody></table><p>当集合类型与 <code>@with_annotated</code> 一起使用时，解析出的值按以下类型传给命令函数：</p><ul>
<li><code>list[T]</code> 传为 <code>list</code></li>
<li><code>set[T]</code> 传为 <code>set</code></li>
<li><code>frozenset[T]</code> 传为 <code>frozenset</code></li>
<li><code>tuple[T, ...]</code> 传为 <code>tuple</code></li>
</ul><p>不支持的模式会抛出 <code>TypeError</code>，包括：</p><ul>
<li>含有多个非 <code>None</code> 成员的联合类型，如 <code>str | int</code>（除非每个成员都是 <code>Enum</code> 子类，例如 <code>EnumA | EnumB</code>，此时按顺序尝试每个成员的转换器）</li>
<li>混合类型的元组，如 <code>tuple[int, str]</code></li>
<li><code>Annotated[T, meta] | None</code>；应改写为 <code>Annotated[T | None, meta]</code></li>
<li><code>Annotated[T, Argument(nargs=N)]</code>，其中 <code>N</code> 为 <code>'*'</code>、<code>'+'</code> 或 <code>&gt;= 1</code> 的整数，而 <code>T</code> 不是集合类型。会产生值列表的 <code>nargs</code> 需要集合注解，如 <code>list[T]</code> 或 <code>tuple[T, ...]</code></li>
<li>可选的固定元数位置参数，如 <code>Annotated[tuple[int, int], Argument()] = (1, 2)</code>、<code>Annotated[tuple[int, int] | None, Argument()]</code>，或任何带默认值或 <code>| None</code> 的位置参数 <code>Argument(nargs=N)</code>。argparse 无法让固定元数的位置参数变为可选（没有表示”缺席或恰好 N 个词法单元”的 <code>nargs</code>），因此请改用可变元数类型如 <code>tuple[T, ...]</code>、去掉默认值，或把它改成选项（给出默认值但不带 <code>Argument()</code>）</li>
<li><code>Annotated[tuple[T, T], Argument(nargs=N)]</code>，其中 <code>N</code> 与元组声明的元素数量不一致。元组类型已经固定了 <code>nargs</code>，用户元数据无法更改它</li>
</ul><p>参数名 <code>dest</code> 和 <code>subcommand</code> 是保留字，不能用作注解参数名。<code>cmd2_statement</code> 接收解析后的 [cmd2.Statement][] 对象，<code>cmd2_subcommand_func</code>（仅在用 <code>@with_annotated(base_command=True)</code> 装饰的命令上有效）接收子命令处理函数。</p></section><section><h2>注解元数据<a href="#注解元数据"><span>#</span></a></h2><p>若需更精细的控制，可使用 <code>typing.Annotated</code> 搭配 [Argument][cmd2.annotated.Argument] 或 [Option][cmd2.annotated.Option] 元数据：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated</span></div></div><div><div><div>2</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Argument, Option, with_annotated</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>sport_choices</span><span>(</span><span>self</span><span>) -&gt; cmd2.Choices:</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span><span> cmd2.Choices.</span><span>from_values</span><span>([</span></span><span>"football"</span><span>, </span><span>"basketball"</span><span>])</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>do_play</span><span>(</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>self</span><span>,</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>sport</span><span>:</span><span> Annotated[</span></div></div><div><div><div>13</div></div><div><span>            </span><span>str</span><span>,</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>Argument</span><span>(</span></div></div><div><div><div>15</div></div><div><span><span>                </span></span><span>choices_provider</span><span>=</span><span>sport_choices,</span></div></div><div><div><div>16</div></div><div><span><span>                </span></span><span>help_text</span><span>=</span><span>"Sport to play"</span><span>,</span></div></div><div><div><div>17</div></div><div><span><span>            </span></span><span>),</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>]</span><span>,</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>venue</span><span>:</span><span> Annotated[</span></div></div><div><div><div>20</div></div><div><span>            </span><span>str</span><span>,</span></div></div><div><div><div>21</div></div><div><span><span>            </span></span><span>Option</span><span>(</span></div></div><div><div><div>22</div></div><div><span>                </span><span>"--venue"</span><span>,</span></div></div><div><div><div>23</div></div><div><span>                </span><span>"-v"</span><span>,</span></div></div><div><div><div>24</div></div><div><span><span>                </span></span><span>help_text</span><span>=</span><span>"Where to play"</span><span>,</span></div></div><div><div><div>25</div></div><div><span><span>                </span></span><span>completer</span><span>=</span><span>cmd2.Cmd.path_complete,</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>),</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>] </span><span>=</span><span> </span><span>"home"</span><span>,</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>):</span></div></div><div><div><div>29</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Playing </span><span>{</span><span>sport</span><span>}</span><span> at </span><span>{</span><span>venue</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>Argument</code> 和 <code>Option</code> 都接受与 <code>add_argument()</code> 相同的 cmd2 专属字段：<code>choices</code>、<code>choices_provider</code>、<code>completer</code>、<code>table_columns</code>、<code>suppress_tab_hint</code>、<code>metavar</code>、<code>nargs</code> 和 <code>help_text</code>。它们还接受用于<a href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BD%AC%E6%8D%A2converter-%E4%B8%8E-preprocess">自定义转换</a>的 <code>converter</code> / <code>preprocess</code>，以及用于<a href="#%E6%9E%9A%E4%B8%BE%E5%88%AB%E5%90%8D%E4%B8%8E%E7%89%B9%E6%AE%8A%E5%85%B3%E9%94%AE%E5%AD%97-allow_unknown_entry">枚举别名</a>的 <code>allow_unknown_entry</code>。</p><p><code>Option</code> 额外接受 <code>action</code>、<code>required</code>，以及用于自定义标志字符串的位置 <code>*names</code>（例如 <code>Option("--color", "-c")</code>）。</p><section><h3>枚举别名与特殊关键字（<code>allow_unknown_entry</code>）<a href="#枚举别名与特殊关键字allow_unknown_entry"><span>#</span></a></h3><p>默认情况下，<code>Enum</code> 参数只接受成员值和成员名。若要让枚举还接受别名、替代拼写或特殊关键字，可在枚举上定义标准的 Python <a href="https://docs.python.org/3/library/enum.html#enum.Enum._missing_" target="_blank"><code>_missing_</code></a> 钩子，并用 <code>allow_unknown_entry=True</code> 显式启用：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> enum</span></div></div><div><div><div>2</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated</span></div></div><div><div><div>3</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Argument, with_annotated</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>Color</span><span>(</span><span>enum</span><span>.</span><span>Enum</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>red </span><span>=</span><span> </span><span>"red"</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>green </span><span>=</span><span> </span><span>"green"</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>blue </span><span>=</span><span> </span><span>"blue"</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>@</span><span>classmethod</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>_missing_</span><span>(</span><span>cls</span><span>,</span><span><span> </span><span>value</span></span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span># 把特殊关键字映射到真实成员；返回 None 则拒绝</span></div></div><div><div><div>14</div></div><div><span>        </span><span>return</span><span> </span><span>cls</span><span>.red </span><span>if</span><span> </span><span>str</span><span><span>(value).</span><span>lower</span><span>() </span><span>==</span><span> </span></span><span>"auto"</span><span> </span><span>else</span><span> </span><span>None</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>19</div></div><div><span>    </span><span>def</span><span> </span><span>do_theme</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>choice</span></span><span>:</span><span><span> Annotated[Color, </span><span>Argument</span><span>(</span><span>allow_unknown_entry</span></span><span>=</span><span><span>True</span><span>)]</span></span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>20</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"theme set to </span><span>{</span><span>choice.value</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>现在 <code>theme auto</code> 会通过 <code>_missing_</code> 解析为 <code>Color.red</code>，而 <code>theme red</code>（值）和 <code>theme green</code>（名）仍照常工作。若 <code>_missing_</code> 拒绝某个词法单元（返回 <code>None</code>），仍会抛出通常的”choose from …”错误。<code>_missing_</code> 自身抛出的任何异常都会原样传播（不会被掩盖成”invalid choice”）。该标志对非 <code>Enum</code> 注解无效；未重写 <code>_missing_</code> 的 <code>Enum</code> 继承默认实现（返回 <code>None</code>），因此该标志对其没有作用。它同样适用于枚举作为集合元素的场景（如 <code>Annotated[list[Color], Argument(allow_unknown_entry=True)]</code>）。</p><p>由于 <code>_missing_</code> 别名是动态的，它们不会加入对外公布的 <code>choices</code>，因此不会出现在 <code>--help</code> 或 Tab 补全中；规范的成员值仍是列出的选项集。</p></section><section><h3>枚举的联合类型<a href="#枚举的联合类型"><span>#</span></a></h3><p>标注为联合类型、且所有成员都是 <code>Enum</code> 子类的参数（例如 <code>EnumA | EnumB</code>）是被接受的。每个成员保留自己的转换器，词法单元按<strong>第一个接受它的成员</strong>解析：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_pick</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>choice</span></span><span>:</span><span> Suit </span><span>|</span><span> Rank</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> </span><span>isinstance</span><span>(choice, Suit):</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"suit </span><span>{</span><span>choice.name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"rank </span><span>{</span><span>choice.name</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>由于解析是”先到先得”，<strong>顺序很重要</strong>：如果一个词法单元是多个成员的合法值（或名称），联合类型中列在前面的成员胜出，后面成员的相同词法单元将变得不可达。<code>allow_unknown_entry</code> 和各成员的 <code>_missing_</code> 钩子仍按成员生效；若某成员的 <code>_missing_</code> 对某个词法单元是<em>抛出异常</em>（而非返回 <code>None</code>），它只是拒绝该词法单元，下一个成员仍会被尝试，且该异常不会中止整个联合类型。只有当所有成员都拒绝时，才会抛出通常的”choose from …”错误。仅支持 <code>Enum</code> 成员；包含 <code>Literal</code> 或任何非 <code>Enum</code> 类型的联合类型仍会因歧义而被拒绝。</p><p>当两个值集有重叠时，请优先选用<a href="#%E6%B3%A8%E8%A7%A3%E5%BC%8F%E5%AD%90%E5%91%BD%E4%BB%A4">类型化子命令</a>（每个子命令一个 <code>Enum</code>），让选择变得明确且无冲突。</p></section><section><h3>动作（action）<a href="#动作action"><span>#</span></a></h3><p>当 <code>Option(action=...)</code> 使用不从命令行取值的零参数 argparse 动作（<code>count</code>、<code>store_true</code>、<code>store_false</code>、<code>store_const</code>、<code>append_const</code>）时，<code>@with_annotated</code> 会在调用 <code>add_argument()</code> 之前剥离它从类型推断出的面向取值的元数据：</p><ul>
<li><code>type</code> 转换器</li>
<li>静态 <code>choices</code></li>
<li>任何推断出的 Tab 补全器（例如 <code>Path</code> 的路径补全器）或 <code>choices_provider</code></li>
</ul><p>这与 argparse 的行为一致（它会拒绝给无值动作配备补全器），并避免诸如把 <code>action='count'</code> 与 <code>type=int</code> 组合这样的解析器构建错误。<em>确实</em>会取值的动作（在 <code>list[T]</code> 上的 <code>append</code> / <code>extend</code>，或普通取值选项）会保留推断出的转换器和补全器。</p><p>在标量 <code>Option</code> 上把 <code>const</code> 与显式 <code>nargs</code> 搭配，会选用 argparse 的可选取值惯用法而非 <code>store_const</code>。<code>Annotated[str | None, Option("--log", nargs='?', const="CONSOLE")]</code> 保留 <code>store</code> 动作和推断出的 <code>type</code> 转换器，因此该标志是三态的：</p><ul>
<li>缺席时得到默认值</li>
<li>只写 <code>--log</code> 时得到 <code>const</code></li>
<li><code>--log VALUE</code> 时得到转换后的 <code>VALUE</code></li>
</ul><p><code>const</code> 按原样存储（不会经过转换器），因此它必须已经符合声明的类型。若没有显式 <code>nargs</code>，仅写 <code>const</code> 仍会推断为无值的 <code>store_const</code>（出现即得到 <code>const</code>，提供取值则是错误）。</p><p><code>Option(action=...)</code> 还接受自定义的 <code>argparse.Action</code> 子类。该类会直接传给 <code>add_argument()</code> 并自行负责解析值的存储，因此类型推断的集合转换以及动作专属的类型/const/形状约束都会被跳过；推断出的 <code>type=</code> 转换器、默认值和 <code>required</code> 仍会应用，该类会像任何手写的 <code>add_argument()</code> 调用一样接收到它们。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>UpperAction</span><span>(</span><span>argparse</span><span>.</span><span>Action</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__call__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>parser</span></span><span>,</span><span><span> </span><span>namespace</span></span><span>,</span><span><span> </span><span>values</span></span><span>,</span><span><span> </span><span>option_string</span></span><span>=</span><span>None</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>setattr</span><span>(namespace, </span><span>self</span><span><span>.dest, values.</span><span>upper</span><span>())</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>@with_annotated</span></div></div><div><div><div>7</div></div><div><span>def</span><span> </span><span>do_shout</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> Annotated[</span><span>str</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--name"</span><span><span>, </span><span>action</span></span><span>=</span><span>UpperAction)] </span><span>=</span><span> </span><span>""</span><span>):</span></div></div><div><div><div>8</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(name)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>@with_annotated</code> 不支持 <code>action='help'</code> 和 <code>action='version'</code>；如需使用请改用 <code>@with_argparser</code>。</p></section><section><h3>保留的关键字参数<a href="#保留的关键字参数"><span>#</span></a></h3><p><code>Argument()</code> 和 <code>Option()</code> 会拒绝一小部分 <code>add_argument()</code> 的关键字参数——这些参数由装饰器从函数签名本身推导而来，这样误用它们会表现为清晰的 <code>TypeError</code>，而不是被静默覆盖。被拒绝的关键字参数包括：</p><ul>
<li><code>type</code> —— 来自参数注解；若需自定义的字符串到值的可调用对象，请使用 <a href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BD%AC%E6%8D%A2converter-%E4%B8%8E-preprocess"><code>converter</code></a>（或用 <code>preprocess</code> 先转换词法单元）</li>
<li><code>dest</code> —— 来自参数名</li>
<li><code>Argument</code> 上的 <code>action</code> 和 <code>required</code> —— 只有 <code>Option</code> 接受它们；位置参数没有 action，且除非带默认值或 <code>| None</code>，否则总是必填</li>
</ul><p>其他所有 <code>add_argument()</code> 参数都会透传，包括通过 [register_argparse_argument_parameter][cmd2.argparse_utils.register_argparse_argument_parameter] 注册的任何自定义参数。</p></section><section><h3>默认值<a href="#默认值"><span>#</span></a></h3><p><code>default</code> 既可以通过函数签名提供，也可以作为元数据关键字参数提供。两种形式等价：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 签名默认值</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_x</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> Annotated[</span><span>str</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--name"</span><span>)] </span><span>=</span><span> </span><span>"HI"</span><span><span>): </span><span>...</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 元数据默认值（行为相同）</span></div></div><div><div><div>6</div></div><div><span>def</span><span> </span><span>do_x</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> Annotated[</span><span>str</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--name"</span><span><span>, </span><span>default</span></span><span>=</span><span>"HI"</span><span>)]</span><span><span>): </span><span>...</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时指定两者属于冲突，会抛出 <code>TypeError</code>。任何来源的 <code>argparse.SUPPRESS</code> 默认值都会被拒绝，因为抑制 namespace 属性会导致调用函数时缺少其期望的关键字参数。</p><p><code>add_help</code>、<code>prefix_chars</code>、<code>fromfile_prefix_chars</code>、<code>argument_default</code>、<code>conflict_handler</code> 和 <code>allow_abbrev</code> 等解析器构建参数不会由 <code>@with_annotated</code> 暴露。请在自定义的 <code>parser_class</code> 子类上设置它们，再通过 <code>parser_class=</code> 传入。</p></section><section><h3>choices 与枚举<a href="#choices-与枚举"><span>#</span></a></h3><p>当为推断出的 <code>Enum</code> 或 <code>Literal</code> 提供了用户自定义的 <code>choices_provider</code> 或 <code>completer</code> 时，推断出的静态 <code>choices</code> 列表会被丢弃，改由 provider 或 completer 驱动补全。推断出的 <code>type</code> 转换器会保留，因此解析值仍会强制转换为声明的类型（<code>Literal[1, 2]</code> 得到 <code>int</code>，<code>Enum</code> 得到其成员），类型之外的值会在解析时被拒绝。</p><p>显式的 <code>choices=</code> 会与推断出的类型相协调，而不是与之冲突：</p><ul>
<li>各值会先经过推断出的 <code>type</code> 转换器，以匹配 argparse 转换后的比较逻辑。<code>Annotated[int, Option("--n", choices=["1", "2"])]</code> 会被规范化为 <code>choices=[1, 2]</code>，因此 <code>--n 1</code> 被接受。若某个 choice 被转换器拒绝（<code>int</code> 上的 <code>choices=["1", "nope"]</code>），会在构建时抛出 <code>TypeError</code>。已经是声明类型的值保持原样。</li>
<li>显式 <code>choices=</code> 优先于<em>类型推断出的</em>补全器（例如 <code>Path</code> 补全器）：choices 会被保留（用于校验和驱动补全），推断出的补全器被丢弃。你自己传入的 <code>choices_provider</code>/<code>completer</code> 仍优先于 <code>choices=</code>。</li>
</ul><p><code>Enum</code> 参数在命令行上同时接受成员<strong>值</strong>和成员<strong>名</strong>（值为 <code>"red"</code> 的 <code>Color.RED</code> 可以用 <code>red</code> 或 <code>RED</code> 选中）；Tab 补全和 <code>--help</code> 列出的是值。</p></section><section><h3>自定义转换（<code>converter</code> 与 <code>preprocess</code>）<a href="#自定义转换converter-与-preprocess"><span>#</span></a></h3><p>使用 <code>@with_argparser</code> 时，你可以把任意可调用对象作为 <code>add_argument(type=...)</code> 来把词法单元解析成自定义值。<code>@with_annotated</code> 则从注解推导 <code>type=</code>，并拒绝元数据中的裸 <code>type=</code>（它会静默遮蔽推断出的转换器）。有两个钩子可以提供同等能力而没有这个坑：</p><ul>
<li><code>converter</code> —— 一个 <code>Callable[[str], Any]</code>，<strong>替换</strong>推断出的 <code>type=</code> 转换器。</li>
<li><code>preprocess</code> —— 一个 <code>Callable[[str], str]</code>，在推断出的转换器<strong>之前</strong>运行。</li>
</ul><p>二者区别在于保留了什么。<code>converter</code> 主管整个转换，因此推断出的 <code>choices</code> 和补全器（它们描述的是推断出的值空间）会被丢弃。<code>preprocess</code> 只转换原始词法单元，因此推断出的 <code>type=</code>、<code>choices</code>、补全器和强制转换全部保留。</p><section><h4><code>converter</code>：替换转换<a href="#converter替换转换"><span>#</span></a></h4><p>当注解内置的转换不适合你的输入，或该类型根本没有内置转换时，使用 <code>converter</code>。由于转换器主管转换，注解不再必须是受支持的标量类型之一——任何类型都合法，通常的”不支持的类型”错误也不会出现：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> datetime</span></div></div><div><div><div>2</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated</span></div></div><div><div><div>3</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Argument, Option, with_annotated</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>def</span><span> </span><span>parse_size</span><span>(</span><span>value</span><span>:</span><span> </span><span>str</span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"""解析带可选 K/M/G 后缀的整数。"""</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>multiplier </span><span>=</span><span> {</span><span>"K"</span><span>: </span><span>1_000</span><span>, </span><span>"M"</span><span>: </span><span>1_000_000</span><span>, </span><span>"G"</span><span>: </span><span>1_000_000_000</span><span><span>}.</span><span>get</span><span>(value[</span><span>-</span></span><span>1</span><span><span>:].</span><span>upper</span><span>(), </span></span><span>1</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>return</span><span> </span><span>int</span><span><span>(value[:</span><span>-</span></span><span>1</span><span>] </span><span>if</span><span><span> multiplier </span><span>!=</span><span> </span></span><span>1</span><span> </span><span>else</span><span><span> value) </span><span>*</span><span> multiplier</span></span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>do_alloc</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>size</span></span><span>:</span><span> Annotated[</span><span>int</span><span><span>, </span><span>Argument</span><span>(</span><span>converter</span></span><span>=</span><span>parse_size)]</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Allocating </span><span>{</span><span>size</span><span>}</span><span> bytes"</span><span>)  </span><span># `alloc 64K` -&gt; 64000</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>18</div></div><div><span>    </span><span>def</span><span> </span><span>do_at</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>when</span></span><span>:</span><span><span> Annotated[datetime.datetime, </span><span>Option</span><span>(</span></span><span>"--when"</span><span><span>, </span><span>converter</span></span><span>=</span><span>datetime.datetime.fromisoformat)]</span><span>):</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(when.</span><span>isoformat</span><span>())  </span></span><span># `datetime` 没有推断出的转换器，converter= 使其合法</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>argparse 按词法单元逐一应用 <code>type=</code>，因此在 <code>list[T]</code> 上转换器会对每个值各运行一次。若要接收<strong>单个</strong>词法单元并让转换器返回整个集合（一到多的惯用法），请标注为非集合类型如 <code>Any</code>——像 <code>set[int]</code> 这样的集合注解会改为推断 <code>nargs</code> 并把输入拆分成多个词法单元：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated, Any</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>parse_intset</span><span>(</span><span>value</span><span>:</span><span> </span><span>str</span><span>) -&gt; set[</span><span>int</span><span>]:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> {</span><span>int</span><span>(piece) </span><span>for</span><span> piece </span><span>in</span><span><span> value.</span><span>split</span><span>(</span></span><span>","</span><span>)}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>@with_annotated</span></div></div><div><div><div>9</div></div><div><span>def</span><span> </span><span>do_select</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>idx</span></span><span>:</span><span><span> Annotated[Any, </span><span>Option</span><span>(</span></span><span>"--idx"</span><span><span>, </span><span>converter</span></span><span>=</span><span>parse_intset)]</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>sorted</span><span>(idx))  </span><span># `select --idx 1,3,5` -&gt; [1, 3, 5]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>显式的 <code>choices=</code> 仍会与转换器相协调（其各值会经过转换器），因此你可以在替换转换后重新加上受限的选项集。</p></section><section><h4><code>preprocess</code>：在推断的转换之前做规范化<a href="#preprocess在推断的转换之前做规范化"><span>#</span></a></h4><p>当推断的转换是正确的、只是原始词法单元需要先加工时，使用 <code>preprocess</code>。推断出的 <code>type=</code>、<code>choices</code> 和补全器会保留，因此 <code>Enum</code> 在接受规范化输入的同时保留其选项和补全，<code>Path</code> 保留其补全器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> os</span></div></div><div><div><div>2</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated</span></div></div><div><div><div>3</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Argument, with_annotated</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>do_tag</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>color</span></span><span>:</span><span><span> Annotated[Color, </span><span>Argument</span><span>(</span><span>preprocess</span></span><span>=</span><span>str</span><span>.lower)]</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>9</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(color.value)  </span></span><span># `tag RED` 可用，`tag &lt;TAB&gt;` 仍会列出颜色</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_open</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>path</span></span><span>:</span><span><span> Annotated[Path, </span><span>Argument</span><span>(</span><span>preprocess</span></span><span>=</span><span>os.path.expanduser)]</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(path)  </span></span><span># `open ~/file` 会展开，路径补全仍可用</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于普通的 <code>str</code>（没有推断出的转换器），<code>preprocess</code> 可调用对象会直接成为 <code>type=</code>。</p><p><code>converter</code> 和 <code>preprocess</code> 在同一参数上<strong>互斥</strong>——转换器已经接收原始词法单元，请把预处理并入其中。二者都不能与无取值动作（<code>store_true</code>、<code>store_false</code>、<code>count</code>、<code>store_const</code>、<code>append_const</code>）组合，因为后者不消费任何需要转换的词法单元。违反者都会在装饰时抛出 <code>TypeError</code>。</p></section></section></section><section><h2>装饰器选项<a href="#装饰器选项"><span>#</span></a></h2><p><code>@with_annotated</code> 目前支持：</p><ul>
<li><code>ns_provider</code> —— 在解析前预填充命名空间，与 <code>@with_argparser</code> 对应</li>
<li><code>preserve_quotes</code> —— 为 <code>True</code> 时保留参数中的引号</li>
<li><code>with_unknown_args</code> —— 为 <code>True</code> 时，无法识别的参数通过 <code>_unknown</code> 传入</li>
<li><code>subcommand_to</code> —— 把该函数注册为某个父命令下的注解式子命令</li>
<li><code>base_command</code> —— 创建基础命令，其解析器同时添加子解析器并暴露 <code>cmd2_subcommand_func</code>。<code>cmd2_subcommand_func</code> 参数只在用 <code>base_command=True</code> 装饰的命令上有效；在别处声明会抛出 <code>TypeError</code></li>
<li><code>subcommand_required</code> —— 是否必须提供子命令（仅在 <code>base_command=True</code> 时有效，默认 <code>True</code>）</li>
<li><code>subcommand_metavar</code> —— 子命令组显示的 metavar（仅在 <code>base_command=True</code> 时有效，默认 <code>"SUBCOMMAND"</code>）</li>
<li><code>subcommand_title</code> —— 子命令 <code>--help</code> 小节的标题（仅在 <code>base_command=True</code> 时有效）。设置它或 <code>subcommand_description</code> 会把子命令从位置参数小节移到独立小节，与 argparse 中的行为一致</li>
<li><code>subcommand_description</code> —— 子命令 <code>--help</code> 小节的描述（仅在 <code>base_command=True</code> 时有效）。只提供它而不提供 <code>subcommand_title</code> 时，该小节使用 argparse 的默认标题 <code>subcommands</code></li>
<li><code>help</code> —— 注解式子命令的帮助文本（仅在配合 <code>subcommand_to</code> 时有效）</li>
<li><code>aliases</code> —— 注解式子命令的别名（仅在配合 <code>subcommand_to</code> 时有效）</li>
<li><code>deprecated</code> —— 在 <code>--help</code> 中把该子命令标记为已弃用（仅在配合 <code>subcommand_to</code> 时有效）</li>
<li><code>groups</code> —— 把参数名分配给参数组的 <code>Group</code> 实例</li>
<li><code>mutually_exclusive_groups</code> —— 互斥参数的 <code>Group</code> 实例</li>
<li><code>parser_class</code> —— 自定义解析器类（默认为配置的默认值）</li>
<li><code>**parser_kwargs</code> —— 其他所有 <code>Cmd2ArgumentParser</code> 构造关键字参数，通过 PEP 692 [<code>Unpack[Cmd2ParserKwargs]</code>][cmd2.annotated.Cmd2ParserKwargs] 转发。完整列表及 <code>description</code> / <code>prog</code> 的特例见下文<a href="#%E8%A7%A3%E6%9E%90%E5%99%A8%E5%AE%9A%E5%88%B6">解析器定制</a></li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span><span>(</span><span>with_unknown_args</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_rawish</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>_unknown</span></span><span>:</span><span> list[</span><span>str</span><span>] </span><span>|</span><span><span> </span><span>None</span><span> </span></span><span>=</span><span><span> </span><span>None</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>((name, _unknown))</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>解析器定制<a href="#解析器定制"><span>#</span></a></h2><p>每个 <code>Cmd2ArgumentParser</code> 构造关键字参数都通过 PEP 692 [<code>Unpack[Cmd2ParserKwargs]</code>][cmd2.annotated.Cmd2ParserKwargs] 直接流经 <code>@with_annotated</code> 和 <code>build_parser_from_function</code>。[<code>Cmd2ParserKwargs</code>][cmd2.annotated.Cmd2ParserKwargs] 这个 <code>TypedDict</code> 是被转发关键字参数的唯一权威来源，并为类型检查器/IDE 在装饰器调用点提供自动补全：给 <code>Cmd2ArgumentParser</code> 新增构造关键字参数只需在 <code>Cmd2ParserKwargs</code> 上添加对应字段，注解式装饰器就会自动识别。</p><p>被转发的关键字参数包括 <code>description</code>、<code>epilog</code>、<code>prog</code>、<code>usage</code>、<code>parents</code>、<code>argument_default</code>、<code>prefix_chars</code>、<code>fromfile_prefix_chars</code>、<code>conflict_handler</code>、<code>add_help</code>、<code>allow_abbrev</code>、<code>exit_on_error</code>、<code>formatter_class</code>、<code>completer_class</code>，以及 Python ≥ 3.14 上的 <code>suggest_on_error</code> / <code>color</code>。其中两个在裸透传之上叠加了额外行为：</p><ul>
<li><code>description</code> —— 省略时会用函数的 docstring 填充（详见下文）；传入显式值可覆盖</li>
<li><code>prog</code> —— 设置了 <code>subcommand_to</code> 时会被拒绝；cmd2 的子命令机制会根据父命令层级重写 <code>prog</code>，这里的任何值都会被静默覆盖</li>
</ul><p><code>parser_class</code> 保持为独立的显式关键字参数，因为它选择的是类本身，而不是传给它的值。参数组通过 [Group][cmd2.annotated.Group] 声明；传入 <code>title</code> 和 <code>description</code> 可得到带标题的帮助小节（省略则为无标题分组）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Group, with_annotated</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>@with_annotated</span><span>(</span></div></div><div><div><div>6</div></div><div><span>        </span><span>description</span><span>=</span><span>"Open a network connection."</span><span>,</span></div></div><div><div><div>7</div></div><div><span>        </span><span>epilog</span><span>=</span><span>"Example: connect example.com --port 2222"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>        </span><span>groups</span><span><span>=</span><span>(</span><span>Group</span><span>(</span></span><span>"host"</span><span><span>,</span><span> </span></span><span>"port"</span><span><span>,</span><span> </span></span><span>title</span><span>=</span><span>"connection"</span><span><span>,</span><span> </span></span><span>description</span><span>=</span><span>"where to connect"</span><span>),),</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>)</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>do_connect</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>host</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>port</span></span><span>:</span><span> </span><span>int</span><span> </span><span>=</span><span><span> </span><span>22</span></span><span>,</span><span><span> </span><span>verbose</span></span><span>:</span><span> </span><span>bool</span><span> </span><span>=</span><span><span> </span><span>False</span></span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"connecting to </span><span>{</span><span>host</span><span>}</span><span>:</span><span>{</span><span>port</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>若省略 <code>description</code>，函数 docstring 的第一段（到第一个空行为止的所有内容）会被用作解析器描述；后续段落会被丢弃，以免 rst 字段指令（如 <code>:param name:</code>）泄漏进 <code>--help</code>。传 <code>description=""</code> 可关闭自动填充，传 <code>description="..."</code> 可覆盖它。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"""Greet someone by name.</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>:param name: who to greet</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>7</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"hello </span><span>{</span><span>name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># parser.description == "Greet someone by name."</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>mutually_exclusive_groups</code> 同样接受 <code>Group</code> 实例。传 <code>Group(..., required=True)</code> 可让互斥组本身成为必填——argparse 会强制要求其成员中恰好有一个被提供。普通（非互斥）<code>Group</code> 上的 <code>required=True</code> 会被拒绝，因为 <code>add_argument_group</code> 没有 <code>required</code> 标志。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>mutually_exclusive_groups</span><span><span>=</span><span>(</span><span>Group</span><span>(</span></span><span>"verbose"</span><span><span>,</span><span> </span></span><span>"quiet"</span><span><span>,</span><span> </span></span><span>required</span><span>=</span><span>True</span><span>),),</span></div></div><div><div><div>3</div></div><div><span>)</span></div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>do_run</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>verbose</span></span><span>:</span><span> </span><span>bool</span><span> </span><span>=</span><span><span> </span><span>False</span></span><span>,</span><span><span> </span><span>quiet</span></span><span>:</span><span> </span><span>bool</span><span> </span><span>=</span><span><span> </span><span>False</span></span><span><span>): </span><span>...</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>给 <code>mutually_exclusive_groups</code> 的 <code>Group</code> 加上 <code>title</code>/<code>description</code>，可以把它渲染为带标题的帮助小节——这是 argparse 唯一支持的嵌套形式：互斥组<em>位于</em>参数组内部。你只需声明一次，无需配对的 <code>groups=</code> 条目。对每个 <code>bool</code> 成员使用 <code>Option(action="store_true")</code>，可让选项显示为 <code>[--json | --csv]</code>，而不是展开成 <code>--json</code>/<code>--no-json</code> 和 <code>--csv</code>/<code>--no-csv</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span><span>(</span></div></div><div><div><div>2</div></div><div><span>    </span><span>mutually_exclusive_groups</span><span><span>=</span><span>(</span><span>Group</span><span>(</span></span><span>"json"</span><span><span>,</span><span> </span></span><span>"csv"</span><span><span>,</span><span> </span></span><span>title</span><span>=</span><span>"output"</span><span><span>,</span><span> </span></span><span>description</span><span>=</span><span>"how to write results"</span><span>),),</span></div></div><div><div><div>3</div></div><div><span>)</span></div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>do_render</span><span>(</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>self</span><span>,</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>json</span><span>:</span><span> Annotated[</span><span>bool</span><span><span>, </span><span>Option</span><span>(</span><span>action</span></span><span>=</span><span>"store_true"</span><span>)] </span><span>=</span><span><span> </span><span>False</span></span><span>,</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>csv</span><span>:</span><span> Annotated[</span><span>bool</span><span><span>, </span><span>Option</span><span>(</span><span>action</span></span><span>=</span><span>"store_true"</span><span>)] </span><span>=</span><span><span> </span><span>False</span></span><span>,</span></div></div><div><div><div>8</div></div><div><span><span>): </span><span>...</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>若要把非互斥参数放进同一小节，请用包含它们全部的 <code>groups=</code> 条目声明，并且不要给互斥组加标题；argparse 会把互斥组嵌套在该组内。在两处都声明该小节、互斥组只有部分成员出现在 <code>groups=</code> 条目中、或它横跨两个条目，都会抛出 <code>ValueError</code>。另外三种嵌套形式（参数组嵌套在另一个组或互斥组内、互斥组嵌套在互斥组内）在 Python 3.14 的 argparse 中已移除，此处也无法表达。</p><p><code>Group</code> 的成员命名的是命令行<strong>参数</strong>。对于普通参数，参数即参数本身，因此名称两边相同。<code>ArgumentBlock</code> 参数则不同：它会展开为每个字段一个参数（字段名 == 参数名），因此你要命名的是它的字段而非整个块：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@dataclass</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>Conn</span><span>(</span><span>ArgumentBlock</span><span>)</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>host: Annotated[</span><span>str</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--host"</span><span><span>)] </span><span>=</span><span> </span></span><span>"localhost"</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>port: Annotated[</span><span>int</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--port"</span><span><span>)] </span><span>=</span><span> </span></span><span>8080</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@with_annotated</span><span>(</span><span>groups</span><span><span>=</span><span>(</span><span>Group</span><span>(</span></span><span>"host"</span><span><span>,</span><span> </span></span><span>"port"</span><span><span>,</span><span> </span></span><span>title</span><span>=</span><span>"connection"</span><span>),))</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>do_connect</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>conn</span></span><span>:</span><span> Conn</span><span>) -&gt; </span><span>None</span><span><span>: </span><span>...</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>给块参数本身命名（<code>Group("conn")</code>）会抛出 <code>ValueError</code>：该参数已被展开，没有属于自己的参数。逐个命名块的字段还有一个好处：你可以指明哪些字段是互斥选项——把 <code>Group("json", "csv")</code> 放进 <code>mutually_exclusive_groups</code>，就只有这两个互相排斥，而不是它们所在块的每个字段都互相排斥。</p><p>分组规格的<em>形状</em>规则——成员重复出现、一个成员被分到两个组、普通组上的 <code>required=True</code>，以及上文的互斥嵌套规则——都在装饰器运行时校验，因此形状不合法的分组会在类定义时抛出 <code>ValueError</code>，而不是等到第一次使用命令时。这些检查只读取 <code>Group</code> 规格，不读取签名或类型注解，因此前向引用的注解仍能正常装饰。</p><p>依赖成员<em>身份</em>的规则（例如每个成员都指向真实参数、互斥组成员必须可省略——即有默认值或是 <code>T | None</code>）在构建解析器时才触发，因为不解析类型注解就无法知道块的字段名。</p><p><code>parents=</code> 与 argparse 标准的 parents 机制一致，用于在多个解析器间共享参数定义。<code>argument_default=argparse.SUPPRESS</code> 不受支持并抛出 <code>TypeError</code>。它会把缺席的参数从解析出的命名空间中移除，但 <code>@with_annotated</code> 是按函数签名构建调用的，因此每个声明的参数在调用时都必须存在；参数从命名空间中消失在这里永远不可能合法（与逐参数的 <code>default=argparse.SUPPRESS</code> 被拒绝相对应）。其他任何 <code>argument_default</code> 值都会原样转发给解析器。</p><p>其余 argparse 关键字参数覆盖较少见的需求，但都原样接入：</p><ul>
<li><code>prefix_chars="+-"</code> 接受以 <code>+</code> 开头的选项（如 <code>+verbose</code>）；配合显式的 <code>Option("+verbose")</code> 来声明此类标志</li>
<li><code>fromfile_prefix_chars="@"</code> 让用户写 <code>mycmd @args.txt</code>，把文件内容作为参数拼接进来</li>
<li><code>conflict_handler="resolve"</code> 让父解析器的选项可以在本地重新定义而不报错——配合 <code>parents=</code> 覆盖继承的标志时很有用</li>
<li><code>add_help=False</code> 移除自动添加的 <code>-h</code>/<code>--help</code> 动作（cmd2 的标准解析器默认保留它）</li>
<li><code>allow_abbrev=False</code> 要求用户输入完整的长选项名（<code>--verbose</code> 不能写成 <code>--verb</code>）</li>
<li><code>exit_on_error=False</code> 让解析失败抛出 <code>argparse.ArgumentError</code> 而不是调用 <code>sys.exit</code>——在把解析器嵌入其他流程时很有用</li>
</ul></section><section><h2>注解式子命令<a href="#注解式子命令"><span>#</span></a></h2><p><code>@with_annotated</code> 还能无需手动构造子解析器就构建类型化的子命令树。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span><span>(</span><span>base_command</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_manage</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span><span>, </span><span>cmd2_subcommand_func</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> cmd2_subcommand_func:</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>cmd2_subcommand_func</span><span>()</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@with_annotated</span><span>(</span><span>subcommand_to</span><span>=</span><span>"manage"</span><span><span>,</span><span> </span></span><span>help</span><span>=</span><span>"list projects"</span><span>)</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>manage_list</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>9</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"listing"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于嵌套子命令，<code>subcommand_to</code> 可以是空格分隔的，例如 <code>subcommand_to="manage project"</code>。中间层级也必须声明为创建自己的子解析器的子命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_annotated</span><span>(</span><span>subcommand_to</span><span>=</span><span>"manage"</span><span><span>,</span><span> </span></span><span>base_command</span><span>=</span><span>True</span><span><span>,</span><span> </span></span><span>help</span><span>=</span><span>"manage projects"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>manage_project</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span><span>, </span><span>cmd2_subcommand_func</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> cmd2_subcommand_func:</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>cmd2_subcommand_func</span><span>()</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@with_annotated</span><span>(</span><span>subcommand_to</span><span>=</span><span>"manage project"</span><span><span>,</span><span> </span></span><span>help</span><span>=</span><span>"add a project"</span><span>)</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>manage_project_add</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>):</span></div></div><div><div><div>9</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"added </span><span>{</span><span>name</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>参数块<a href="#参数块"><span>#</span></a></h2><p>当多个命令共享同一组参数时，可复用的<em>参数块</em>（argument block）能消除重复。在 <code>@dataclass</code> 上继承 <code>cmd2.ArgumentBlock</code> 特性类，并用它注解某个参数。每个字段成为一个扁平的命令行参数（字段名 == 参数名），解析出的值会被重建成 dataclass 实例传给命令：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> dataclasses </span><span>import</span><span> dataclass</span></div></div><div><div><div>2</div></div><div><span>from</span><span> typing </span><span>import</span><span> Annotated</span></div></div><div><div><div>3</div></div><div><span>from</span><span> pathlib </span><span>import</span><span> Path</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>6</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> with_annotated</span></div></div><div><div><div>7</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> Option</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>@dataclass</span></div></div><div><div><div>11</div></div><div><span>class</span><span><span> </span><span>CommonArgs</span><span>(</span><span>cmd2</span><span>.</span><span>ArgumentBlock</span><span>)</span></span><span>:</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>verbose: Annotated[</span><span>bool</span><span><span>, </span><span>Option</span><span>(</span></span><span>"-v"</span><span>, </span><span>"--verbose"</span><span><span>)] </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>output: Annotated[Path </span><span>|</span><span> </span><span>None</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--output"</span><span><span>)] </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>@with_annotated</span></div></div><div><div><div>18</div></div><div><span>    </span><span>def</span><span> </span><span>do_build</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>target</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>common</span></span><span>:</span><span> CommonArgs</span><span>):</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"</span><span>{</span><span>target</span><span>}</span><span> verbose=</span><span>{</span><span>common.verbose</span><span>}</span><span> output=</span><span>{</span><span>common.output</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>build app --verbose --output /tmp/x</code> 会重建出 <code>CommonArgs(verbose=True, output=Path("/tmp/x"))</code> 并作为 <code>common</code> 传入。块参数本身永远不是参数，只有它的字段才是。</p><p>字段携带常规的 <code>Annotated[T, Option(...)]</code> / <code>Annotated[T, Argument(...)]</code> 元数据，其行为与同形状的顶层参数完全一致。dataclass 是默认值的唯一权威来源：带默认值（<code>default</code> 或 <code>default_factory</code>）的字段在调用时由 dataclass 构造器填充，因此 <code>default_factory</code> 每次调用都会产生新值，<code>__post_init__</code> 也会运行。没有默认值的字段成为必填参数。</p><p>继承是复用机制：块的子类本身也是块，因此可以在共享基块的基础上按命令扩展，而无需重复其字段。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@dataclass</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>TracedArgs</span><span>(</span><span>CommonArgs</span><span>)</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>trace: </span><span>bool</span><span><span> </span><span>=</span><span> </span></span><span>False</span><span>  </span><span># do_test 会得到 verbose、output 和 trace</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>几条规则保证块的语义无歧义：</p><ul>
<li>触发条件是 <code>ArgumentBlock</code> 特性类，而非”是 dataclass”。普通 <code>@dataclass</code> 不受影响，仍可作为普通的单值使用（例如通过 <code>Argument(converter=...)</code>）</li>
<li>块必须是普通参数的<em>裸</em>注解。把它包进 <code>Annotated</code>/<code>Optional</code>/联合类型，或用作 <code>*args</code>/<code>**kwargs</code>，都会抛出清晰的错误</li>
<li>由于字段是扁平展开的，字段名与其他参数或其他块的字段冲突时，会在构建解析器时报错，而不是静默共享同一个目标名</li>
<li>与块参数同名的字段（例如接收为 <code>opts: Opts</code> 的块上的 <code>opts</code>）会被拒绝：字段的扁平参数与接收块实例的参数会共享同一个目标名</li>
<li>字段的默认值必须写在 dataclass 字段上（<code>= value</code> 或 <code>= field(default_factory=...)</code>），不能写在 <code>Option</code>/<code>Argument</code> 元数据中，也不能通过动作默认值（<code>append</code>/<code>count</code>）提供。默认值由 dataclass 掌管以保证每次调用得到新值，因此在没有 dataclass 默认值的字段上提供元数据或动作默认值会被拒绝</li>
<li>类型本身是块的字段不会被展开（不递归），会作为不支持的类型被拒绝</li>
</ul><section><h3>与子命令共享块（<code>cmd2_base_args</code> / <code>cmd2_parent_args</code>）<a href="#与子命令共享块cmd2_base_args--cmd2_parent_args"><span>#</span></a></h3><p>基础命令及其子命令解析到同一个共享命名空间。若要沿命令链向下共享某个块，请在拥有这些标志的命令上把参数命名为 <code>cmd2_base_args</code>，在每个应接收它的子命令上命名为 <code>cmd2_parent_args</code>，两处都用同一个块类型注解：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@dataclass</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>SharedOpts</span><span>(</span><span>cmd2</span><span>.</span><span>ArgumentBlock</span><span>)</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>verbose: Annotated[</span><span>bool</span><span><span>, </span><span>Option</span><span>(</span></span><span>"-v"</span><span>, </span><span>"--verbose"</span><span><span>)] </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>level: Annotated[</span><span>int</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--level"</span><span><span>)] </span><span>=</span><span> </span></span><span>1</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>@with_annotated</span><span>(</span><span>base_command</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>def</span><span> </span><span>do_root</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>cmd2_subcommand_func</span></span><span>,</span><span><span> </span><span>cmd2_base_args</span></span><span>:</span><span> SharedOpts</span><span>):</span></div></div><div><div><div>10</div></div><div><span>        </span><span>if</span><span> cmd2_subcommand_func:</span></div></div><div><div><div>11</div></div><div><span><span>            </span></span><span>cmd2_subcommand_func</span><span>()</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>@with_annotated</span><span>(</span><span>subcommand_to</span><span>=</span><span>"root"</span><span><span>,</span><span> </span></span><span>help</span><span>=</span><span>"show the inherited block"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>root_show</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>cmd2_parent_args</span></span><span>:</span><span> SharedOpts</span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"verbose=</span><span>{</span><span>cmd2_parent_args.verbose</span><span>}</span><span> level=</span><span>{</span><span>cmd2_parent_args.level</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>root --verbose --level 5 show</code> 输出 <code>verbose=True level=5</code>：在 <code>root</code> 上解析的选项以类型化方式流入子命令，无需重新声明。<code>cmd2_base_args</code> 把块的标志添加到自己命令的解析器上，而 <code>cmd2_parent_args</code> <em>不</em>添加任何参数，它由祖先解析出的值重建（<code>root --verbose show</code>，而不是 <code>root show --verbose</code>）。若某子命令使用了 <code>cmd2_parent_args</code>，但其祖先从未声明匹配的 <code>cmd2_base_args</code>，它第一次运行时会抛出清晰的错误。这是通过 <code>ns_provider</code> 转发父级状态的类型化替代方案。</p><p>每个共享块字段都解析到一个由其块类型限定的目标名，因此同一条命令链上不同层级的两个不同类型的 <code>cmd2_base_args</code> 块可以使用相同的字段名而不冲突。继承到的块总是接收自己类型的值，无论中间层级上另一个块的同名字段解析出了什么。在两个层级复用同一块类型会共享一个目标名，因此设置该标志的最近层级胜出。</p><p>子命令也可以在继承的块之外声明自己的普通块。二者相互独立：继承块的标志在父命令上提供，而子命令自己的块把它的标志添加到子命令的解析器上。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@dataclass</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>RunOpts</span><span>(</span><span>cmd2</span><span>.</span><span>ArgumentBlock</span><span>)</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>retries: Annotated[</span><span>int</span><span><span>, </span><span>Option</span><span>(</span></span><span>"--retries"</span><span><span>)] </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>@with_annotated</span><span>(</span><span>base_command</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>do_root</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>cmd2_subcommand_func</span></span><span>,</span><span><span> </span><span>cmd2_base_args</span></span><span>:</span><span> SharedOpts</span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span>if</span><span> cmd2_subcommand_func:</span></div></div><div><div><div>10</div></div><div><span><span>            </span></span><span>cmd2_subcommand_func</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>@with_annotated</span><span>(</span><span>subcommand_to</span><span>=</span><span>"root"</span><span>)</span></div></div><div><div><div>13</div></div><div><span>    </span><span>def</span><span> </span><span>root_run</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>cmd2_parent_args</span></span><span>:</span><span> SharedOpts</span><span>,</span><span><span> </span><span>run</span></span><span>:</span><span> RunOpts</span><span>):</span></div></div><div><div><div>14</div></div><div><span>        </span><span># --verbose/--level 来自 `root`；--retries 是本子命令自己的标志</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"run </span><span>{</span><span>name</span><span>}</span><span> verbose=</span><span>{</span><span>cmd2_parent_args.verbose</span><span>}</span><span> retries=</span><span>{</span><span>run.retries</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>root --verbose run job --retries 3</code> 会在 <code>root</code> 上解析 <code>--verbose</code>、在 <code>run</code> 上解析 <code>--retries</code>。</p></section></section><section><h2>底层解析器构建<a href="#底层解析器构建"><span>#</span></a></h2><p>[cmd2.annotated.build_parser_from_function][cmd2.annotated.build_parser_from_function] 可以直接从函数构建解析器，而无需注册命令。它接受与 <code>@with_annotated</code> 相同的 <code>groups</code>、<code>mutually_exclusive_groups</code>、<code>parser_class</code> 和转发的 [<code>Unpack[Cmd2ParserKwargs]</code>][cmd2.annotated.Cmd2ParserKwargs]。与装饰器一样，它会跳过第一个参数作为方法接收者（<code>self</code>/<code>cls</code>）。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2.annotated </span><span>import</span><span> build_parser_from_function</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>,</span><span><span> </span><span>count</span></span><span>:</span><span> </span><span>int</span><span> </span><span>=</span><span><span> </span><span>1</span></span><span>):</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""Greet someone."""</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>parser </span><span>=</span><span> </span><span>build_parser_from_function</span><span>(greet)</span></span></div></div><div><div><div>9</div></div><div><span><span>namespace </span><span>=</span><span> parser.</span><span>parse_args</span><span>([</span></span><span>"Alice"</span><span>, </span><span>"--count"</span><span>, </span><span>"3"</span><span>])</span></div></div><div><div><div>10</div></div><div><span># namespace.name == "Alice", namespace.count == 3</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>基于类型的自动补全<a href="#基于类型的自动补全"><span>#</span></a></h2><p>使用 <code>@with_annotated</code> 时，标注为 <code>Path</code> 或 <code>Enum</code> 的参数无需显式的 <code>choices_provider</code> 或 <code>completer</code> 即可获得自动补全。</p><p>具体来说：</p><ul>
<li><code>Path</code>（或任何 <code>Path</code> 子类）触发文件系统路径补全</li>
<li><code>MyEnum</code>（任何 <code>enum.Enum</code> 子类）触发基于枚举成员值的补全</li>
</ul><p>使用 <code>@with_argparser</code> 时，若需要补全行为，请显式提供 <code>choices</code>、<code>choices_provider</code> 或 <code>completer</code>。</p></section><section><h2>稳定性与反馈<a href="#稳定性与反馈"><span>#</span></a></h2><p>由于该功能是实验性的：</p><ul>
<li>边缘情况的行为（混合类型元组、深层嵌套的 <code>Annotated</code>、冲突的元数据）可能发生变化</li>
<li>诊断性错误消息的措辞可能调整</li>
<li>支持的类型注解集合可能扩充或缩减</li>
</ul><p>如果你依赖 <code>@with_annotated</code>，请通过 <a href="https://github.com/python-cmd2/cmd2/issues" target="_blank">issue tracker</a> 分享反馈和边缘案例，以便在该功能结束实验性状态之前把行为固定下来。</p></section></section>
<section><h1>API 参考：cmd2.annotated<a href="#api-参考cmd2annotated"><span>#</span></a></h1><p>官方文档中的 API 参考页由 mkdocstrings 从 <code>cmd2.annotated</code> 模块的 docstring 自动生成（源文件仅包含 <code>::: cmd2.annotated</code> 指令）。其主要公开成员如下：</p><ul>
<li>[cmd2.annotated.with_annotated][cmd2.with_annotated] —— 根据类型注解构建解析器的命令装饰器</li>
<li>[cmd2.annotated.Argument][] —— 位置参数的 <code>typing.Annotated</code> 元数据类</li>
<li>[cmd2.annotated.Option][] —— 选项参数的 <code>typing.Annotated</code> 元数据类</li>
<li>[cmd2.annotated.Group][] —— 用于参数组与互斥组的分组规格</li>
<li>[cmd2.annotated.ArgumentBlock][] —— 参数块所继承的 dataclass 特性类</li>
<li>[cmd2.annotated.build_parser_from_function][] —— 不注册命令、直接从函数构建解析器</li>
<li>[cmd2.annotated.Cmd2ParserKwargs][] —— 转发给 <code>Cmd2ArgumentParser</code> 构造器的关键字参数 <code>TypedDict</code></li>
</ul><p>各成员的完整签名与逐参数说明请参阅 <a href="https://cmd2.readthedocs.io/en/latest/api/cmd2.html" target="_blank">cmd2 官方 API 文档</a>或源码 docstring。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/table_creation/</id>
      <title type="text">Python cmd2：表格创建</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/table_creation/"/>
      <summary type="text">说明自 cmd2 3.0.0 起表格创建交由 rich 库支持，并提供相关文档与示例链接。</summary>
      <content type="html"><![CDATA[<section><h1>表格创建<a href="#表格创建"><span>#</span></a></h1><p>从 3.0.0 版本开始，<code>cmd2</code> 不再包含自定义的表格创建代码。</p><p>这是因为 <code>cmd2</code> 现在依赖于 <a href="https://github.com/Textualize/rich" target="_blank">rich</a>，它对这一功能有出色的支持。</p><p>请参阅 rich 的<a href="https://rich.readthedocs.io/en/latest/tables.html" target="_blank">表格文档</a>了解更多信息。</p><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_tables.py" target="_blank">rich_tables.py</a> 示例演示了如何在 <code>cmd2</code> 应用中使用 <code>rich</code> 表格。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/theme/</id>
      <title type="text">Python cmd2：主题</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/theme/"/>
      <summary type="text">介绍如何使用 cmd2.theme.update_theme 为应用配置整体主题，以及自定义 Tab 补全菜单颜色的方法。</summary>
      <content type="html"><![CDATA[<section><h1>主题<a href="#主题"><span>#</span></a></h1><p><code>cmd2</code> 提供了使用 [cmd2.theme.update_theme][] 函数为应用配置整体主题的能力。这基于 <a href="https://rich.readthedocs.io/en/stable/reference/theme.html" target="_blank">rich.theme</a> 样式信息容器。你可以使用它来为应用打造品牌，并设置对用户群体有吸引力的整体一致外观。</p><section><h2>自定义补全菜单颜色<a href="#自定义补全菜单颜色"><span>#</span></a></h2><p><code>cmd2</code> 利用 <code>prompt-toolkit</code> 实现其 Tab 补全菜单。你可以通过在 <code>cmd2</code> 主题中重写以下样式来自定义补全菜单的颜色：</p><ul>
<li><code>Cmd2Style.COMPLETION_MENU</code> - 整个补全菜单容器的基础样式（设置背景）</li>
<li><code>Cmd2Style.COMPLETION_MENU_COMPLETION</code> - 单个未选中补全项的样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_CURRENT</code> - 当前选中补全项的样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_META</code> - 补全项旁边显示的”元信息”样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_META_CURRENT</code> - 当前选中项的元信息样式</li>
</ul><p>默认情况下，当前选中的补全项和元数据使用绿色背景上的黑色文本来提供对比度。所有其他项默认保持 <code>prompt-toolkit</code> 的默认值。然而，<code>cmd2</code> 应用作者可以自由地自定义这些样式，以匹配所需的视觉风格和/或品牌。</p></section><section><h2>示例<a href="#示例"><span>#</span></a></h2><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py" target="_blank">rich_theme.py</a> 了解为 <code>cmd2</code> 应用配置自定义主题的简单示例。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/mixins/</id>
      <title type="text">Python cmd2：混入类（Mixin）详解</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/mixins/"/>
      <summary type="text">介绍 cmd2 的混入类（Mixin）机制，包括混入类的初始化顺序、添加命令与设置、重写方法以及注册钩子等最佳实践。</summary>
      <content type="html"><![CDATA[<section><h1>混入类（Mixin）<a href="#混入类mixin"><span>#</span></a></h1><section><h2>混入类基础<a href="#混入类基础"><span>#</span></a></h2><p>在 Python 中，混入（mixin）是一种通过多重继承向其他类提供特定功能集合的类。混入不打算被单独实例化；相反，它们的作用是将行为”混合”或组合到一个基类中，而不建立僵化的”是一个（is-a）“关系。</p><p>关于混入类的更多信息，我们推荐这篇 <code>Real Python</code> 文章：<a href="https://realpython.com/python-mixin/" target="_blank">What Are Mixin Classes in Python?</a></p></section><section><h2>cmd2 混入类概览<a href="#cmd2-混入类概览"><span>#</span></a></h2><p>如果你有一组可复用的行为，希望应用于多个不同的 <code>cmd2</code> 应用，那么创建一个混入类来封装这些行为是个好主意。这是依赖多重继承来扩展 <code>cmd2</code> 的一种方式。它快速而简单，但有一些潜在的陷阱需要了解，以便你知道如何正确使用。</p><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/mixin.py" target="_blank">mixins.py</a> 示例展示了如何为 <code>cmd2</code> 应用开发混入类。过去我们称这些为”插件（Plugins）“，但回想起来这可能不是最好的名字。它们本质上是混入类，为继承自 [cmd2.Cmd][] 的类添加一些额外功能。</p></section><section><h2>使用本模板<a href="#使用本模板"><span>#</span></a></h2><p>本文件提供了一个非常基础的模板，说明你如何创建自己的 cmd2 混入类，将可复用的行为封装起来，通过多重继承应用于多个 <code>cmd2</code> 应用。</p></section><section><h2>命名<a href="#命名"><span>#</span></a></h2><p>如果你决定将混入发布为 Python 包，应考虑给项目名称加上 <code>cmd2-</code> 前缀。采用这种方式后，项目内部应有一个带 <code>cmd2_</code> 前缀的包。</p></section><section><h2>添加功能<a href="#添加功能"><span>#</span></a></h2><p>有许多方式可以通过混入为 <code>cmd2</code> 添加功能。混入是一个封装并将代码注入另一个类的类。在 <code>cmd2</code> 项目中使用混入的开发者，会将混入的代码注入到他们继承 [cmd2.Cmd][] 的子类中。</p><section><h3>混入类与初始化<a href="#混入类与初始化"><span>#</span></a></h3><p>下面这个简短示例展示了如何创建混入类，以及所有内容是如何初始化的。</p><p>混入类：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyMixin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 初始化之前运行</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 初始化之后运行</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用该混入的示例应用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>Example</span><span>(</span><span>MyMixin</span><span>, </span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""展示如何使用混入类的 cmd2 应用类。"""</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 或</span></div></div><div><div><div>9</div></div><div><span>        </span><span># 任何混入初始化之前运行</span></div></div><div><div><div>10</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>11</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 和</span></div></div><div><div><div>12</div></div><div><span>        </span><span># 所有混入初始化之后运行</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意混入必须在 <code>cmd2.Cmd</code> 之前被继承（或混入）。这有两个原因：</p><ul>
<li>Python 标准库中的 <code>cmd.Cmd.__init__()</code> 方法不会调用 <code>super().__init__()</code>。由于这一疏漏，如果你不先继承 <code>MyMixin</code>，<code>MyMixin.__init__()</code> 方法将永远不会被调用。</li>
<li>你可能希望混入能够重写 <code>cmd2.Cmd</code> 的方法。如果你在 <code>cmd2.Cmd</code> 之后混入混入类，Python 的方法解析顺序（MRO）会先调用 <code>cmd2.Cmd</code> 的方法，再调用混入中的方法。</li>
</ul></section><section><h3>添加命令<a href="#添加命令"><span>#</span></a></h3><p>你的混入可以添加用户可见的命令。在混入中的做法与在 <code>cmd2.Cmd</code> 应用中完全相同：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyMixin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>do_say</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>statement</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>"""简单的 say 命令"""</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(statement)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在混入中你拥有与 <code>cmd2.Cmd</code> 应用中相同的所有能力，包括通过装饰器进行参数解析和自定义帮助方法。</p></section><section><h3>添加（或隐藏）设置<a href="#添加或隐藏设置"><span>#</span></a></h3><p>混入可以向应用添加用户可控制的设置。示例如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyMixin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 初始化之前运行</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># 此处的代码在 cmd2.Cmd 初始化之后运行</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.mysetting </span><span>=</span><span> </span></span><span>"somevalue"</span></div></div><div><div><div>7</div></div><div><span>        </span><span>self</span><span><span>.settable.</span><span>update</span><span>({</span></span><span>"mysetting"</span><span>: </span><span>"short help message for mysetting"</span><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你也可以通过从 <code>self.settable</code> 中移除的方式对用户隐藏设置。</p></section><section><h3>装饰器<a href="#装饰器"><span>#</span></a></h3><p>你的混入可以提供装饰器，供混入的使用者将其包装在自己的命令周围以附加功能。</p></section><section><h3>重写方法<a href="#重写方法"><span>#</span></a></h3><p>你的混入可以重写 <code>cmd2.Cmd</code> 的核心方法，改变其行为。这种方式应谨慎使用，因为它非常脆弱。如果开发者在应用中使用多个混入，且多个混入重写了同一个方法，只有最先被混入的混入的重写方法会被调用。</p><p>钩子是好得多的方案。</p></section><section><h3>钩子<a href="#钩子"><span>#</span></a></h3><p>混入可以注册钩子，由 <code>cmd2.Cmd</code> 在应用和命令处理生命周期的各个时点调用。混入不应重写任何遗留的 <code>cmd</code> 钩子方法，而应按照 <code>cmd2</code> 文档中的<a href="features/hooks/">描述</a>注册自己的钩子。</p><p>你应该以混入的名称作为钩子名称的开头。钩子方法会被混入 <code>cmd2</code> 应用，这种命名约定有助于避免无意间的方法重写。</p><p>一个简单示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyMixin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># 此处的代码在 cmd2 初始化之前运行</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># 此处的代码在 cmd2 初始化之后运行</span></div></div><div><div><div>6</div></div><div><span>        </span><span># 在这里注册你的钩子函数</span></div></div><div><div><div>7</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_postparsing_hook</span><span>(</span></span><span>self</span><span>.cmd2_mymixin_postparsing_hook)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>def</span><span> </span><span>cmd2_mymixin_postparsing_hook</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>data</span></span><span>:</span><span> cmd2.plugin.PostparsingData</span><span>) -&gt; cmd2.plugin.PostparsingData:</span></div></div><div><div><div>10</div></div><div><span>        </span><span>"""在解析用户输入之后、运行命令之前调用的方法"""</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"in postparsing_hook"</span><span>)</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span> data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注册方式允许多个混入（甚至应用本身）各自注入代码，在应用或命令处理生命周期期间被调用。</p><p>完整的应用和命令生命周期信息，包括所有可用钩子以及钩子影响生命周期的方式，参阅 <a href="features/hooks/">cmd2 钩子文档</a>。</p></section><section><h3>类与函数<a href="#类与函数"><span>#</span></a></h3><p>你的混入还可以提供类和函数，供基于 <code>cmd2</code> 的应用开发者使用。请在文档中描述这些类和函数，让混入的用户知道有哪些可用。</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/testing/</id>
      <title type="text">Python cmd2：测试指南</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/testing/"/>
      <summary type="text">介绍为 cmd2 应用编写单元测试和集成测试时的特殊注意事项，包括运行测试套件、测试命令以及 Mock 的正确用法。</summary>
      <content type="html"><![CDATA[<section><h1>测试<a href="#测试"><span>#</span></a></h1><section><h2>概述<a href="#概述"><span>#</span></a></h2><p>本文介绍为 cmd2 应用编写单元测试或集成测试时的特殊注意事项。</p></section><section><h2>运行 cmd2 的测试套件<a href="#运行-cmd2-的测试套件"><span>#</span></a></h2><p>运行 <code>make test</code> 可执行测试套件，并附带覆盖率统计和 pytest-xdist 并行执行。默认的 <code>-n auto</code> 会根据可用 CPU 数自动选择工作进程数，并发运行相互独立的测试。同样的设置也适用于 <code>uv run pytest</code> 和 CI。</p><p>如果只想运行某个特定测试或进行交互式调试，可以禁用并行执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>uv</span><span> </span><span>run</span><span> </span><span>pytest</span><span> </span><span>-n</span><span> </span><span>0</span><span> </span><span>tests/test_history.py</span></div></div><div><div><div>2</div></div><div><span>uv</span><span> </span><span>run</span><span> </span><span>pytest</span><span> </span><span>-n</span><span> </span><span>0</span><span> </span><span>--no-cov</span><span> </span><span>--pdb</span><span> </span><span>tests/test_history.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可以用 <code>-n 4</code> 之类的方式覆盖工作进程数。覆盖率会针对 <code>cmd2</code> 收集，并输出到终端、<code>coverage.xml</code> 和 <code>htmlcov/</code>。每次调用都会重新开始统计；如果希望合并多次运行的结果，请显式传入 <code>--cov-append</code>。</p><p>终端测试替身（test double）应当对光标位置查询作出应答，除非测试本身针对的就是缺失应答的情况。并发测试应使用事件同步，而不是任意的 sleep；当超时路径是被断言的对象时，应缩短仅用于测试的过期时间。对于依赖其他线程推进的等待，应保留足够宽裕的超时边界。</p></section><section><h2>测试命令<a href="#测试命令"><span>#</span></a></h2><p>我们鼓励 <code>cmd2</code> 应用开发者参考 <a href="https://github.com/python-cmd2/cmd2/tree/main/tests" target="_blank">cmd2 tests</a> 中的示例，了解如何对 <code>cmd2</code> 命令进行单元测试和集成测试。其中提供了多种辅助工具，可以捕获并返回 stdout、stderr 以及命令特有的结果数据。</p></section><section><h2>Mock（模拟）<a href="#mock模拟"><span>#</span></a></h2><p>如果你需要在 cmd2 应用中 Mock 任何对象——尤其是在 [cmd2.Cmd][] 或 [cmd2.CommandSet][] 的子类中——必须使用 <a href="https://docs.python.org/3/library/unittest.mock.html#autospeccing" target="_blank">Autospeccing</a>、<a href="https://docs.python.org/3/library/unittest.mock.html#patch" target="_blank">spec=True</a>，或你所用 Mock 库提供的等价机制。</p><p>为了自动将函数加载为命令，<code>cmd2</code> 会执行大量反射调用来查找你在 cmd2 应用中定义的类的属性。许多 Mock 库会为请求的任何属性自动创建 Mock 对象，而不管该属性是否真实存在于被 Mock 的对象上。这种行为可能错误地引导 cmd2 把某个函数或属性当作它需要识别和处理的东西。为避免这种情况，你应该始终启用 <a href="https://docs.python.org/3/library/unittest.mock.html#autospeccing" target="_blank">Autospeccing</a> 或 <a href="https://docs.python.org/3/library/unittest.mock.html#patch" target="_blank">spec=True</a> 来进行 Mock。如果没有开启 autospeccing，你的单元测试会失败，并出现类似下面的错误信息：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cmd2.exceptions.CommandSetRegistrationError:</span><span> </span><span>Subcommand</span></div></div><div><div><div>2</div></div><div><span>&lt;MagicMock</span><span> </span><span>name='cmdloop.subcommand_name'</span><span> </span><span>id='4506146416'&gt;</span><span> </span><span>is</span><span> </span><span>not</span><span> </span><span>valid:</span><span> </span><span>must</span><span> </span><span>be</span><span> </span><span>a</span><span> </span><span>string.</span></div></div><div><div><div>3</div></div><div><span>Received</span><span> </span><span>&lt;class</span><span> </span><span>'unittest.mock.MagicMock'&gt;</span><span> </span><span>instead</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>示例<a href="#示例"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>test_mocked_methods</span><span>():</span></div></div><div><div><div>2</div></div><div><span>    </span><span>with</span><span><span> mock.patch.</span><span>object</span><span>(MockMethodApp, </span></span><span>'foo'</span><span>, </span><span>spec</span><span>=</span><span>True</span><span>):</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>cli </span><span>=</span><span> </span><span>MockMethodApp</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>另一个示例使用 <a href="https://pypi.org/project/pytest-mock" target="_blank">pytest-mock</a> 提供的 <code>mocker</code> fixture：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>test_mocked_methods2</span><span>(</span><span>mocker</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>mock_cmdloop </span><span>=</span><span> mocker.</span><span>patch</span><span>(</span><span>"cmd2.Cmd.cmdloop"</span><span>, </span><span>autospec</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>cli </span><span>=</span><span> cmd2.</span><span>Cmd</span><span>()</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>cli.</span><span>cmdloop</span><span>()</span></div></div><div><div><div>5</div></div><div><span>    </span><span>assert</span><span><span> mock_cmdloop.call_count </span><span>==</span><span> </span></span><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/upgrades/</id>
      <title type="text">Python cmd2：大版本升级指南</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/upgrades/"/>
      <summary type="text">介绍 cmd2 各大版本之间的升级要点，包括从 2.x 升级到 3.x 以及从 3.x 升级到 4.x 的破坏性变更与迁移方法。</summary>
      <content type="html"><![CDATA[<section><h1>cmd2 大版本升级<a href="#cmd2-大版本升级"><span>#</span></a></h1><section><h2>从 3.x 升级到 cmd2 4.x<a href="#从-3x-升级到-cmd2-4x"><span>#</span></a></h2><p>从 3.x 到 4.x 最大的变化是从 GNU Readline 库迁移到 <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">prompt-toolkit</a> 来实现 REPL（读取-求值-打印循环）。这一变更提供了 readline 的纯 Python 替代品，完全跨平台兼容，并在功能和可扩展性方面带来了显著增强。</p><section><h3>prompt-toolkit 迁移<a href="#prompt-toolkit-迁移"><span>#</span></a></h3><p><code>cmd2</code> 现在使用 <code>prompt-toolkit</code> 处理所有输入、历史导航和 Tab 补全。这移除了对 GNU Readline 库（以及 macOS 上的 <code>gnureadline</code> 包）的依赖。</p><section><h4>prompt-toolkit 的主要优势<a href="#prompt-toolkit-的主要优势"><span>#</span></a></h4><ul>
<li><strong>跨平台</strong>：在 Windows、macOS 和 Linux 上行为完全一致，无需外部依赖。</li>
<li><strong>异步输出</strong>：更好地支持向终端异步打印，同时不干扰用户的输入行。</li>
<li><strong>增强的 UI</strong>：支持底部工具栏、浮动菜单等高级 UI 元素。</li>
<li><strong>多行编辑</strong>：改进了对跨多行命令的编辑支持。</li>
</ul></section><section><h4>破坏性变更与不兼容之处<a href="#破坏性变更与不兼容之处"><span>#</span></a></h4><p>尽管我们努力保持兼容，但仍存在一些差异：</p><ul>
<li><strong>键绑定</strong>：键绑定现在由 <code>prompt-toolkit</code> 管理。虽然默认仍是 Emacs 风格绑定（与 readline 类似），但自定义方式改为使用 <code>prompt-toolkit</code> 的 <a href="https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/key_bindings.html" target="_blank">KeyBindings</a> API。</li>
<li><strong>输入钩子</strong>：不再支持 readline 特有的输入钩子。</li>
</ul></section></section><section><h3>底部工具栏<a href="#底部工具栏"><span>#</span></a></h3><p><code>cmd2</code> 现在支持可选的持久底部工具栏，可用于显示应用名称、当前状态甚至实时时钟等信息。</p><ul>
<li><strong>启用</strong>：在 <code>cmd2.Cmd.__init__</code> 构造函数中设置 <code>enable_bottom_toolbar=True</code>。</li>
<li><strong>自定义</strong>：重写 <code>cmd2.Cmd.get_bottom_toolbar</code> 方法，返回你希望显示的内容。</li>
</ul><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例，了解如何实现定期刷新工具栏的后台线程。</p></section><section><h3>自定义补全菜单颜色<a href="#自定义补全菜单颜色"><span>#</span></a></h3><p><code>cmd2</code> 现在使用 <code>prompt-toolkit</code> 实现 Tab 补全菜单，并可通过 <code>cmd2</code> 主题自定义其外观。</p><ul>
<li><strong>自定义</strong>：使用 <code>cmd2.theme.update_theme()</code> 覆盖 <code>Cmd2Style.COMPLETION_MENU_CURRENT</code> 和 <code>Cmd2Style.COMPLETION_MENU_META</code> 样式。详见<a href="features/theme/#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A5%E5%85%A8%E8%8F%9C%E5%8D%95%E9%A2%9C%E8%89%B2">自定义补全菜单颜色</a>。</li>
</ul></section><section><h3>已删除的模块<a href="#已删除的模块"><span>#</span></a></h3><p>移除了 <code>rl_utils.py</code> 和 <code>terminal_utils.py</code>，因为 <code>prompt-toolkit</code> 已提供这些功能。</p></section></section><section><h2>从 2.x 升级到 cmd2 3.x<a href="#从-2x-升级到-cmd2-3x"><span>#</span></a></h2><p>关于 3.0.0 版本所有变更的详细信息，请参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/CHANGELOG.md" target="_blank">CHANGELOG.md</a>。</p><p>从 2.x 到 3.x 最大的变化是 <code>cmd2</code> 现在依赖 <a href="https://github.com/Textualize/rich" target="_blank">rich</a>。相应地，<code>cmd2</code> 依靠 <code>rich</code> 在终端中进行精美的文本样式设置和格式化。因此，<code>cmd2</code> 中大量自定义代码被移除，其他内容也有所变动或改为基于 <code>rich</code> 实现。</p><p>升级到 3.x 时用户应注意的主要变化详述如下各小节。</p><section><h3>已删除的模块<a href="#已删除的模块-1"><span>#</span></a></h3><section><h4>ansi<a href="#ansi"><span>#</span></a></h4><p><code>cmd2.ansi</code> 模块中的功能或已被移除，或改为基于 <code>rich</code> 并移动到以下新模块之一：<code>cmd2.colors</code>、<code>cmd2.string_utils</code> 或 <code>cmd2.styles</code>。</p><p>为了简化从 <code>cmd2</code> 2.x 到 3.x 的迁移路径，我们以独立包的形式创建了 <code>cmd2-ansi</code> 模块，它是 <code>cmd2</code> 2.7.0 中 <code>cmd2.ansi</code> 模块的向后移植。相关链接：</p><ul>
<li>PyPI：<a href="https://pypi.org/project/cmd2-ansi/" target="_blank">cmd2-ansi</a></li>
<li>GitHub：<a href="https://github.com/python-cmd2/cmd2-ansi" target="_blank">cmd2-ansi</a></li>
</ul><p>使用此向后移植包：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2_ansi </span><span>import</span><span> ansi</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>table_creator<a href="#table_creator"><span>#</span></a></h4><p><code>cmd2.table_creator</code> 模块已不复存在。更多信息请参阅 rich 关于 <a href="https://rich.readthedocs.io/en/latest/tables.html" target="_blank">Tables</a> 的文档。<a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_tables.py" target="_blank">rich_tables.py</a> 示例演示了如何在 <code>cmd2</code> 应用中使用 <code>rich</code> 表格。</p><p><code>rich</code> 表格提供了比 <code>cmd2</code> 之前所提供的更强大的能力和灵活性。对于这一向后不兼容我们深表歉意，但两者的 API 根本不同，我们无法设计出一个向后兼容的包装层。</p><p>为了简化从 <code>cmd2</code> 2.x 到 3.x 的迁移路径，我们以独立包的形式创建了 <code>cmd2-table</code> 模块，它是 <code>cmd2</code> 2.7.0 中 <code>cmd2.table_creator</code> 模块的向后移植。相关链接：</p><ul>
<li>PyPI：<a href="https://pypi.org/project/cmd2-table/" target="_blank">cmd2-table</a></li>
<li>GitHub：<a href="https://github.com/python-cmd2/cmd2-table" target="_blank">cmd2-table</a></li>
</ul><p>使用此向后移植包：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2_table </span><span>import</span><span> table_creator</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>新增模块<a href="#新增模块"><span>#</span></a></h3><section><h4>colors<a href="#colors"><span>#</span></a></h4><p>新的 <code>cmd2.colors</code> 模块提供了便捷的 <code>cmd2.colors.Color</code> <code>StrEnum</code> 类，用于表示 <code>rich</code> 颜色名称。这让你可以在代码中使用可 Tab 补全的常量，而不是魔法字符串来表示所需的确切颜色。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 了解使用 <code>Color</code> 类为输出选择颜色的基础示例。或者参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/color.py" target="_blank">color.py</a> 示例，直观查看所有受支持的颜色。</p></section><section><h4>rich_utils<a href="#rich_utils"><span>#</span></a></h4><p>新的 <code>cmd2.rich_utils</code> 模块提供了在 <code>cmd2</code> 应用中支持 <code>rich</code> 使用的通用工具类和函数。其中大部分内容并非面向最终用户。</p><p>许多 <code>cmd2</code> 应用开发者可能会感兴趣的是 <code>cmd2.theme.update_theme</code> 函数。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py" target="_blank">rich_theme.py</a> 示例，了解如何为你的应用设置主题（配色方案）。</p></section><section><h4>styles<a href="#styles"><span>#</span></a></h4><p>错误消息等信息的默认显示样式现在位于新的 <code>cmd2.styles</code> 模块中，并且基于 <code>rich</code> 样式。</p><p>此前 <code>cmd2</code> 的默认样式位于 <code>cmd2.ansi</code> 模块中。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_completion.py" target="_blank">argparse_completion.py</a> 示例，了解如何在 <code>cmd2</code> 应用中利用这些默认样式来保持一致的外观。</p></section><section><h4>string_utils<a href="#string_utils"><span>#</span></a></h4><p>各种字符串工具函数已从 <code>cmd2.ansi</code> 模块移至新的 <code>cmd2.string_utils</code> 模块。</p><p>其中包括用于文本样式化、对齐以及加引号/去引号的函数。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例，了解如何使用通用的 <code>cmd2.string_utils.stylize</code> 函数。</p></section><section><h4>terminal_utils<a href="#terminal_utils"><span>#</span></a></h4><p>设置窗口标题、异步警报等终端控制转义序列的支持已从 <code>cmd2.ansi</code> 移入主 <code>cmd2.Cmd</code> 应用类中。</p><p>这部分并非供最终用户直接使用，而是供 <code>cmd2.Cmd.set_window_title</code> 和 <code>cmd2.Cmd.add_alert</code> 等面向最终用户的高层功能使用。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/async_printing.py" target="_blank">async_printing.py</a> 了解如何在 <code>cmd2</code> 应用中使用此功能。</p></section></section><section><h3>Argparse HelpFormatter 类<a href="#argparse-helpformatter-类"><span>#</span></a></h3><p><code>cmd2</code> 现在有 5 个不同的 Argparse HelpFormatter 类，它们全部基于 <a href="https://github.com/hamdanal/rich-argparse" target="_blank">rich-argparse</a> 的 <code>RichHelpFormatter</code> 类：</p><ul>
<li><code>cmd2.rich_utils.Cmd2HelpFormatter</code></li>
<li><code>cmd2.rich_utils.ArgumentDefaultsCmd2HelpFormatter</code></li>
<li><code>cmd2.rich_utils.MetavarTypeCmd2HelpFormatter</code></li>
<li><code>cmd2.rich_utils.RawDescriptionCmd2HelpFormatter</code></li>
<li><code>cmd2.rich_utils.RawTextCmd2HelpFormatter</code></li>
</ul><p>此前默认的 <code>Cmd2HelpFormatter</code> 类继承自 <code>argparse.RawTextHelpFormatter</code>，但现在它继承自 <code>argparse.HelpFormatter</code>。如果你想要 RawText 行为，请向解析器传入 <code>formatter_class=RawTextCmd2HelpFormatter</code>。</p><p>好处是你的 <code>cmd2</code> 应用现在拥有更赏心悦目的帮助信息，包含颜色，能让你更快速、更轻松地目视解析帮助文本。这适用于所有受支持的 Python 版本。</p></section><section><h3>其他变更<a href="#其他变更"><span>#</span></a></h3><ul>
<li><code>cmd2.Cmd.__init__</code> 的 <code>auto_load_commands</code> 参数现在默认为 <code>False</code></li>
<li>用更 Python 风格的 <code>value</code> 属性取代了 <code>Settable.get_value()</code> 和 <code>Settable.set_value()</code> 方法</li>
<li>移除了 <code>with_argparser()</code> 装饰器中对解析器 <code>prog</code> 值的冗余设置，因为现在由 <code>Cmd._build_parser()</code> 统一处理</li>
</ul></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/</id>
      <title type="text">Python cmd2：构建强大的命令行解释器</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/"/>
      <summary type="text">介绍 Python cmd2 包的使用方法，用于构建强大的命令行解释器（CLI）程序，扩展 Python 标准库的 cmd 模块。</summary>
      <content type="html"><![CDATA[<p>本系列文档基于 <a href="https://cmd2.readthedocs.io/en/latest/" target="_blank">cmd2 官方文档</a> 翻译和修改，适用于 cmd2&gt;=4.0.0</p>
<a href="https://github.com/python-cmd2/cmd2" target="_blank"><div><div><div><div></div><div>python-cmd2</div></div><div>/</div><div>cmd2</div></div><div></div></div><div>cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python</div><div><div>—</div><div>—</div><div>MIT</div><span>Python</span></div></a>
<section><h1>cmd2：构建强大的命令行解释器<a href="#cmd2构建强大的命令行解释器"><span>#</span></a></h1><p>cmd2 是一个 Python 包，用于构建强大的命令行解释器（CLI）程序。它扩展了 Python 标准库的 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 模块。</p><p><code>cmd2</code> 的基本用法与 <code>cmd</code> 模块完全相同。</p><section><h2>快速开始<a href="#快速开始"><span>#</span></a></h2><ol>
<li>创建一个 <code>cmd2.Cmd</code> 的子类。定义属性和 <code>do_*</code> 方法来控制其行为。
在本文档中，我们假设你将子类命名为 <code>App</code>：</li>
</ol><div><figure><figcaption><span>创建继承自 cmd2.Cmd 的类</span></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span># 自定义属性和方法</span></div></div><div><div><div>5</div></div><div><span>    </span><span>pass</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>实例化 <code>App</code> 并启动命令循环：</li>
</ol><div><figure><figcaption><span>实例化并启动 cmd2 应用</span></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span># 自定义属性和方法</span></div></div><div><div><div>5</div></div><div><span>    </span><span>pass</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>app </span><span>=</span><span> </span><span>App</span><span>()</span></span></div></div><div><div><div>8</div></div><div><span><span>app.</span><span>cmdloop</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>cmd2 入门指南<a href="#cmd2-入门指南"><span>#</span></a></h1><p>正在构建一个新的 <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" target="_blank">REPL（读取-求值-打印循环）</a> 或 <a href="https://en.wikipedia.org/wiki/Command-line_interface" target="_blank">命令行界面（CLI）</a> 应用程序吗？</p><p>已经构建了一个使用 Python 标准库中 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 模块的应用程序，并希望通过很少的工作添加更多功能吗？</p><p><code>cmd2</code> 是一个用于构建命令行应用程序的强大 Python 库。从这里开始，了解这个库是否适合你的需求。</p><section><h2>为什么选择 cmd2？<a href="#为什么选择-cmd2"><span>#</span></a></h2><p><code>cmd2</code> 提供了许多开箱即用的功能，使构建命令行应用程序变得简单而强大：</p><ol>
<li><strong>自动补全</strong> - 为命令和参数提供智能自动补全</li>
<li><strong>历史记录</strong> - 自动保存和检索命令历史</li>
<li><strong>帮助系统</strong> - 自动生成帮助文档</li>
<li><strong>参数解析</strong> - 强大的参数解析和验证</li>
<li><strong>输出重定向</strong> - 支持将输出重定向到文件</li>
<li><strong>别名系统</strong> - 创建命令别名以提高效率</li>
<li><strong>多命令支持</strong> - 在一行中执行多个命令</li>
</ol></section><section><h2>安装说明<a href="#安装说明"><span>#</span></a></h2><p><code>cmd2</code> 可在 Linux、macOS 和 Windows 上运行。它需要 Python 3.11 或更高版本、<a href="https://pypi.org/project/pip" target="_blank">pip</a> 和 <a href="https://pypi.org/project/setuptools" target="_blank">setuptools</a>。如果你已经具备这些条件，那么可以直接运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>根据你在系统上安装 Python 的方式、位置以及使用的操作系统，你可能需要管理员或 root 权限才能安装 Python 包。如果是这种情况，请采取必要的步骤以 root/管理员身份运行本节中的命令，例如：在大多数 Linux 或 Mac 系统上，你可以在命令前加上 <code>sudo</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>sudo</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>&lt;package_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div><div><div><div></div><div>Tip</div></div><div><p>你也可以使用其他 Python 包管理器，如 <a href="https://github.com/astral-sh/uv" target="_blank">uv</a>，但这超出了本安装指南的范围。<code>cmd2</code> 开发者非常喜欢并强烈推荐 <code>uv</code>，并且 <code>cmd2</code> 本身的开发也使用它。但如果你是一位足够资深的 Python 开发者，已经在使用 <code>uv</code>，那么你就不需要我们来告诉你如何使用它了</p></div></div><section><h3>先决条件<a href="#先决条件"><span>#</span></a></h3><p>如果你从 <a href="https://www.python.org" target="_blank">python.org</a> 安装了 Python &gt;=3.11，你将已经拥有 <a href="https://pypi.org/project/pip" target="_blank">pip</a> 和 <a href="https://pypi.org/project/setuptools" target="_blank">setuptools</a>，但可能需要升级到最新版本：</p><p>在 Linux 或 OS X 上：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>-U</span><span> </span><span>pip</span><span> </span><span>setuptools</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 Windows 上：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>C:\</span><span>&gt; </span><span>python</span><span> </span><span>-m</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>-U</span><span> </span><span>pip</span><span> </span><span>setuptools</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>从 PyPI 安装<a href="#从-pypi-安装"><span>#</span></a></h3><p><a href="https://pypi.org/project/pip" target="_blank">pip</a> 是推荐的安装器。使用 pip 从 <a href="https://pypi.org" target="_blank">PyPI</a> 安装包非常简单：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这将安装所需的第三方依赖项（如果需要）。</p></section><section><h3>从 GitHub 安装<a href="#从-github-安装"><span>#</span></a></h3><p>最新版本的 <code>cmd2</code> 可以直接从 GitHub 的 main 分支使用 <a href="https://pypi.org/project/pip" target="_blank">pip</a> 安装：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>pip</span><span> </span><span>install</span><span> </span><span>-U</span><span> </span><span>git+https://github.com/python-cmd2/cmd2.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>从 Debian 或 Ubuntu 仓库安装<a href="#从-debian-或-ubuntu-仓库安装"><span>#</span></a></h3><p>我们建议从 <a href="https://pypi.org/project/pip" target="_blank">pip</a> 安装，但如果你希望从 Debian 或 Ubuntu 仓库安装，可以使用 apt-get。</p><p>对于 Python 3：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ sudo apt-get install python3-cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也将安装所需的第三方依赖项。</p><div><div><div></div><div>Warning</div></div><div><p>2.0.0 之前的 <code>cmd2</code> 版本应被视为不稳定的 “beta” 质量，不应依赖于生产环境使用。如果你无法从操作系统仓库获取 &gt;= 2.0.0 的版本，那么我们建议从 PyPI 或 GitHub 安装 - 参见 [Pip 安装](#从 PyPI 安装) 或 [从 GitHub 安装](#从 GitHub 安装)。</p></div></div></section><section><h3>升级 cmd2<a href="#升级-cmd2"><span>#</span></a></h3><p>将已安装的 <code>cmd2</code> 升级到 <a href="https://pypi.org" target="_blank">PyPI</a> 的最新版本：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pip install -U cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这将升级到 <code>cmd2</code> 的最新稳定版本，并在必要时升级任何依赖项。</p></section><section><h3>卸载 cmd2<a href="#卸载-cmd2"><span>#</span></a></h3><p>如果你希望永久卸载 <code>cmd2</code>，也可以使用 <a href="https://pypi.org/project/pip" target="_blank">pip</a> 轻松完成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ pip uninstall cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>将 cmd2 集成到你的项目中<a href="#将-cmd2-集成到你的项目中"><span>#</span></a></h2><p>安装完成后，你需要确保项目的依赖项中包含 <code>cmd2</code>。请确保你的 <code>pyproject.toml</code> 或 <code>setup.py</code> 包含以下依赖项：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>'cmd2&gt;=3,&lt;4'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>cmd2</code> 项目使用 <a href="https://semver.org" target="_blank">语义化版本控制</a>，这意味着任何不兼容的 API 更改都将以新的主版本号发布。公共 API 在 <a href="https://cmd2.readthedocs.io/en/latest/api/" target="_blank">API 参考</a> 中有文档记录。</p><p>我们建议你遵循 Python 打包用户指南中关于 <a href="https://packaging.python.org/discussions/install-requires-vs-requirements/" target="_blank">install_requires</a> 的建议。通过设置允许版本的上限，你可以确保你的项目不会无意中安装了不兼容的未来版本 <code>cmd2</code>。</p></section><section><h2>快速示例<a href="#快速示例"><span>#</span></a></h2><p>以下是一个简单的 <code>cmd2</code> 应用程序示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""一个简单的 cmd2 应用示例"""</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>do_greet</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>"""问候用户"""</span></div></div><div><div><div>8</div></div><div><span>        </span><span>if</span><span> args:</span></div></div><div><div><div>9</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"你好，</span><span>{</span><span>args</span><span>}</span><span>！"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>11</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"你好！"</span><span>)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>def</span><span> </span><span>do_quit</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>14</div></div><div><span>        </span><span>"""退出应用程序"""</span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span><span> </span><span>True</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>'__main__'</span><span>:</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>MyApp</span><span>()</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>app.</span><span>cmdloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>替代方案<a href="#替代方案"><span>#</span></a></h2><p>对于不与用户进行连续循环交互的程序——那些只是简单地从命令行接受一组参数、返回结果，而不将用户保持在程序环境中的程序——你只需要 <a href="https://docs.python.org/3/library/sys.html" target="_blank">sys</a>.argv（命令行参数）和 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a>（用于解析 UNIX 风格的选项和标志）。尽管有些人可能更喜欢 <a href="https://pypi.org/project/docopt" target="_blank">docopt</a> 或 <a href="https://click.palletsprojects.com" target="_blank">click</a> 而不是 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a>。</p><p><a href="https://textual.textualize.io/" target="_blank">textual</a> 模块能够构建复杂的全屏终端用户界面（TUI），这些界面不仅限于简单的文本输入和输出；它们可以用选项绘制屏幕，这些选项可以通过光标键甚至鼠标点击来选择。然而，编写 <code>textual</code> 应用程序并不像使用 <code>cmd2</code> 那样简单直接。</p><p>存在几个 Python 包用于构建交互式命令行应用程序，其概念与 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 应用程序大致相似。它们都没有 <code>cmd2</code> 与 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 的紧密联系，但它们可能值得研究。</p><p><a href="https://click.palletsprojects.com" target="_blank">Click</a> 是一个 Python 包，用于以可组合的方式创建美观的命令行界面，只需尽可能少的代码。它更倾向于命令行实用程序而不是命令行解释器，但两者都可以使用。</p><p>要获得一个基于 <a href="https://click.palletsprojects.com" target="_blank">Click</a> 的工作命令解释器应用程序，需要比 <code>cmd2</code> 更多的努力和样板代码。<code>cmd2</code> 专注于提供出色的开箱即用体验，尽可能多地内置有用功能，同时要求开发者尽可能少的工作量。我们相信 <code>cmd2</code> 为开发者提供了编写命令行解释器的最简单方式，同时为最终用户提供良好的体验。</p><p>历史上，<a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">Python Prompt Toolkit</a> 被认为是 <code>cmd2</code> 的一个强大但更复杂的替代品。然而，从 4.0.0 版本开始，<code>cmd2</code> 在内部使用 <code>prompt-toolkit</code> 作为其 REPL 引擎。这意味着你可以获得 <code>prompt-toolkit</code> 的强大功能和跨平台兼容性，以及 <code>cmd2</code> 易于使用的 API。</p><p>如果你正在寻求更丰富的视觉最终用户体验，并且不介意投入更多开发时间，我们建议查看 <a href="https://github.com/Textualize/textual" target="_blank">Textual</a>，因为它可以用来在终端中构建非常复杂的用户界面，这些界面更类似于功能丰富的 Web GUI。</p></section><section><h2>资源<a href="#资源"><span>#</span></a></h2><p>项目相关链接和其他资源：</p><ul>
<li><a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a></li>
<li><a href="https://github.com/python-cmd2/cmd2" target="_blank">cmd2 项目页面</a></li>
<li><a href="https://github.com/python-cmd2/cmd2/issues" target="_blank">项目错误跟踪器</a></li>
<li><a href="https://cmd2.readthedocs.io/en/latest/api/" target="_blank">API 参考</a></li>
<li><a href="#%E5%85%A5%E9%97%A8%E7%A4%BA%E4%BE%8B">入门示例应用</a></li>
</ul></section><section><h2>下一步<a href="#下一步"><span>#</span></a></h2><p>现在你已经了解了 <code>cmd2</code> 的基本概念，可以：</p><ol>
<li>按照上述安装说明安装 <code>cmd2</code></li>
<li>查看<a href="#%E5%85%A5%E9%97%A8%E7%A4%BA%E4%BE%8B">入门示例应用</a>了解更完整的示例</li>
</ol></section></section>
<section><h1>从 cmd 升级到 cmd2<a href="#从-cmd-升级到-cmd2"><span>#</span></a></h1><p>如果你正在考虑将你的 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 应用程序升级为基于 <code>cmd2</code>，本节将帮助你决定它是否适合你的应用程序，并展示如何进行。</p><section><h2>为什么选择 cmd2<a href="#为什么选择-cmd2-1"><span>#</span></a></h2><section><h3>cmd<a href="#cmd"><span>#</span></a></h3><p><a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 是 Python 标准库中用于创建简单交互式命令行应用程序的模块。<a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 是一个极其简单的框架，有很多不足之处。它甚至不包含内置的退出应用程序的方式！</p><p>由于 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 提供的 API 是 <code>cmd2</code> 的基础，理解 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 的使用是学习 <code>cmd2</code> 的第一步。阅读完 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 文档后，请返回此处了解 <code>cmd2</code> 与 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 的不同之处。</p></section><section><h3>cmd2<a href="#cmd2"><span>#</span></a></h3><p><code>cmd2</code> 是 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 的功能齐全的扩展，提供了丰富的功能，使开发者能够更快、更轻松地创建功能丰富、令用户满意的交互式命令行应用程序。</p><p><code>cmd2</code> 可以作为 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 的直接替代品使用，但有一些小的差异，详见<a href="#%E4%B8%8D%E5%85%BC%E5%AE%B9%E6%80%A7">不兼容性</a>部分。只需将 <code>cmd2</code> 导入替代 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 即可为应用程序添加许多功能，无需进一步修改。迁移到 <code>cmd2</code> 还将为开发者提供更多可能性，为用户提供一流的交互式命令行体验。</p><div><div><div></div><div>Warning</div></div><div><p>从版本 4.0.0 开始，<code>cmd2</code> 不再实际依赖于 <code>cmd</code>。<code>cmd2</code> 与 <code>cmd</code> 大部分 API 兼容。
请参阅<a href="#%E4%B8%8D%E5%85%BC%E5%AE%B9%E6%80%A7">不兼容性</a>了解少数已记录的不兼容性。</p></div></div></section><section><h3>自动功能<a href="#自动功能"><span>#</span></a></h3><p>从 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 切换到 <code>cmd2</code> 后，你的应用程序将自动获得以下新功能和能力，无需任何额外工作：</p><ul>
<li>更强大的<a href="features/history/">历史记录</a>。<a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 和 <code>cmd2</code> 都提供熟悉的 readline 风格历史记录，但 <code>cmd2</code> 使用强大的 <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">prompt-toolkit</a> 库，提供纯 Python、完全跨平台的体验。此外，<code>cmd2</code> 有一个强大的 <code>history</code> 命令，允许你在选择的文本编辑器中编辑先前的命令、一次重新运行多个命令、将先前的命令保存为脚本以供稍后执行等等。</li>
<li>用户可以将输出重定向到文件或通过管道传递给其他操作系统命令。你记得在所有打印函数中使用 <code>self.stdout</code> 而不是 <code>sys.stdout</code> 吗？如果是这样，这将开箱即用。如果不是，你需要回去修复它们。在修复之前，你应该考虑 <code>cmd2</code> 提供的各种<a href="features/embedded_output_help/#%E7%94%9F%E6%88%90%E8%BE%93%E5%87%BA">生成输出</a>方式。</li>
<li>用户可以加载<a href="features/scripting/">脚本文件</a>，其中包含要执行的一系列命令。</li>
<li>用户可以创建<a href="features/shortcuts_aliases_macros/">快捷方式、别名和宏</a>来减少重复命令所需的输入。</li>
<li><a href="features/embedded_output_help/#%E5%B5%8C%E5%85%A5%E5%BC%8F-python-shell">嵌入式 Python 和/或 IPython shell</a> 允许用户在你的 <code>cmd2</code> 应用程序中执行 Python 代码。</li>
<li><a href="features/clipboard/">剪贴板集成</a> 允许你将命令输出保存到操作系统剪贴板。</li>
<li>内置的<a href="features/misc_packaging/#%E8%AE%A1%E6%97%B6%E5%99%A8">计时器</a> 可以显示命令执行所需的时间。</li>
</ul></section></section><section><h2>不兼容性<a href="#不兼容性"><span>#</span></a></h2><p><code>cmd2</code> 努力与 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 保持直接兼容，但也有一些不兼容之处。</p><section><h3>Cmd.emptyline()<a href="#cmdemptyline"><span>#</span></a></h3><p><a href="https://docs.python.org/3/library/cmd.html#cmd.Cmd.emptyline" target="_blank">Cmd.emptyline()</a> 函数在响应提示输入空行时被调用。默认情况下，在 <code>cmd</code> 中，如果未覆盖此方法，它会重复并执行最后输入的非空命令。然而，我们遇到的最终用户都不认为这是期望或可取的默认行为。<code>cmd2</code> 完全忽略空行，基类 <code>cmd.emptyline()</code> 方法永远不会被调用，因此无法覆盖空行行为。</p></section><section><h3>Cmd.identchars<a href="#cmdidentchars"><span>#</span></a></h3><p>在 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 中，<a href="https://docs.python.org/3/library/cmd.html#cmd.Cmd.identchars" target="_blank">Cmd.identchars</a> 属性包含命令名称接受的字符字符串。<a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 使用这些字符来分割输入的第一个”单词”，无需用户输入空格。例如，如果 <code>identchars</code> 包含所有字母字符的字符串，用户可以输入像 <code>L20</code> 这样的命令，它将被解释为命令 <code>L</code>，第一个参数为 <code>20</code>。</p><p>从版本 0.9.0 开始，<code>cmd2</code> 忽略了 <code>identchars</code>；<code>cmd2</code> 中的解析逻辑在空白处分割命令和参数。我们选择这个破坏性更改是因为虽然 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 支持 unicode，但在命令名称中使用非 ASCII unicode 字符同时使用 <code>identchars</code> 功能可能会有些痛苦。要求使用空格分隔参数也确保了 <code>cmd2</code> 许多其他有用功能的可靠运行，包括<a href="features/command_completion_disable/#%E8%A1%A5%E5%85%A8">制表符补全</a>和<a href="features/shortcuts_aliases_macros/">快捷方式、别名和宏</a>。</p><p>如果你确实需要在应用程序中使用此功能，可以通过编写<a href="features/hooks/#%E8%A7%A3%E6%9E%90%E5%90%8E%E9%92%A9%E5%AD%90">解析后钩子</a>来添加回来。</p></section><section><h3>Cmd.cmdqueue<a href="#cmdcmdqueue"><span>#</span></a></h3><p>在 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 中，<a href="https://docs.python.org/3/library/cmd.html#cmd.Cmd.cmdqueue" target="_blank">Cmd.cmdqueue</a> 属性包含排队的输入行列表。当 <code>cmdloop()</code> 需要新输入时会检查 cmdqueue 列表；如果非空，其元素将按顺序处理，就像在提示符下输入一样。</p><p>从版本 0.9.13 开始，<code>cmd2</code> 移除了对 <code>Cmd.cmdqueue</code> 的支持。由于 <code>cmd2</code> 支持通过主 <code>cmdloop()</code>、文本脚本、Python 脚本和历史记录重放运行命令，要在这些方法之间保持一致行为的唯一方法是消除命令队列。此外，没有这个队列的存在，推理应用程序行为要容易得多。</p></section></section><section><h2>最低要求更改<a href="#最低要求更改"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 提供了标准库中 <a href="https://docs.python.org/3/library/cmd.html#cmd.Cmd" target="_blank">cmd.Cmd</a> 的所有相同公共方法和字段，除了 <code>Cmd.emptyline</code>（<code>cmd2</code> 从不调用此方法）。大多数基于标准库的应用程序只需几分钟即可迁移到 <code>cmd2</code>。</p><section><h3>导入和继承<a href="#导入和继承"><span>#</span></a></h3><p>你需要将导入从：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>改为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后将类定义从：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>CmdLineApp</span><span>(</span><span>cmd</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>改为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>CmdLineApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>退出<a href="#退出"><span>#</span></a></h3><p>查看你创建的用于退出应用程序的命令。你可能有一个名为 <code>exit</code> 的命令，也许还有一个类似的名为 <code>quit</code> 的命令。你可能还实现了 <code>do_EOF()</code> 方法，以便你的程序像许多操作系统 shell 一样退出。如果所有这些命令都只是退出应用程序，你可以删除它们。请参阅<a href="features/misc_packaging/#%E9%80%80%E5%87%BA%E6%96%B9%E5%BC%8F">退出</a>。</p></section><section><h3>分发<a href="#分发"><span>#</span></a></h3><p>如果你要分发应用程序，还需要确保正确安装了 <code>cmd2</code>。你需要在 <code>pyproject.toml</code> 或 <code>setup.py</code> 中添加以下依赖项：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>'cmd2&gt;=3,&lt;4'</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>后续步骤<a href="#后续步骤"><span>#</span></a></h2><p>一旦你当前的应用程序开始使用 <code>cmd2</code>，你可以通过利用其他 <code>cmd2</code> 功能来扩展功能。以下三个想法可以帮助你入门。</p><section><h3>参数解析<a href="#参数解析"><span>#</span></a></h3><p>对于除最简单命令之外的所有命令，使用 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a> 解析用户输入可能比为每个命令手动解析更容易。<code>cmd2</code> 提供了一个 <code>@with_argparser()</code> 装饰器，它将 <code>Cmd2ArgumentParser</code> 对象与你的命令关联。使用此方法将：</p><ol>
<li>将包含参数的 <a href="https://docs.python.org/3/library/argparse.html#argparse.Namespace" target="_blank">Namespace</a> 传递给你的命令，而不是文本字符串</li>
<li>正确处理来自用户的带引号的字符串输入</li>
<li>基于 <code>Cmd2ArgumentParser</code> 为你创建帮助消息</li>
<li>为你的应用程序添加<a href="features/command_completion_disable/#%E8%A1%A5%E5%85%A8">制表符补全</a>提供巨大帮助</li>
<li>使实现子命令变得更加容易（例如 <code>git</code> 有许多子命令，如 <code>git pull</code>、<code>git diff</code> 等）</li>
</ol><p>如果你想进一步了解，可以查看更多关于<a href="features/argument_processing/">参数处理</a>的内容。</p></section><section><h3>帮助<a href="#帮助"><span>#</span></a></h3><p>如果你的应用程序中有许多命令，<code>cmd2</code> 可以使用一行装饰器 <code>@with_category()</code> 对这些命令进行分类。当用户输入 <code>help</code> 时，可用命令将按你指定的类别组织。</p><p>如果你已经在使用 <code>argparse</code> 或决定切换到它，你可以轻松地<a href="features/argument_processing/#%E5%B8%AE%E5%8A%A9%E6%B6%88%E6%81%AF">标准化所有帮助消息</a>，使其由参数解析器生成并由 <code>cmd2</code> 显示。不再有与代码实际功能不匹配的帮助消息。</p></section><section><h3>生成输出<a href="#生成输出"><span>#</span></a></h3><p>如果你的程序通过直接打印到 <code>sys.stdout</code> 生成输出，你应该考虑切换到 <code>cmd2.Cmd.poutput</code>、<code>cmd2.Cmd.perror</code> 和 <code>cmd2.Cmd.pfeedback</code>。这些方法与几个内置的<a href="features/settings_plugins/">设置</a>配合使用，允许用户查看或抑制反馈（即进度或状态输出）。它们还根据用户偏好正确处理 ANSI 彩色输出。<code>cmd2</code> 对 :simple-rich: <a href="https://github.com/Textualize/rich" target="_blank">rich</a> 的依赖使你可以轻松地为输出添加颜色和样式。请参阅<a href="features/embedded_output_help/#%E5%BD%A9%E8%89%B2%E8%BE%93%E5%87%BA">彩色输出</a>部分了解更多详情。这些及其他相关主题在<a href="features/embedded_output_help/#%E7%94%9F%E6%88%90%E8%BE%93%E5%87%BA">生成输出</a>中有介绍。</p></section></section><section><h2>总结<a href="#总结"><span>#</span></a></h2><p>迁移到 <code>cmd2</code> 是一个相对简单的过程，可以为你的应用程序带来许多强大的功能。通过遵循本指南中的步骤，你可以快速将现有的 <code>cmd</code> 应用程序升级为功能更强大的 <code>cmd2</code> 应用程序，为用户提供更好的交互体验。</p></section></section>
<section><h1>入门示例<a href="#入门示例"><span>#</span></a></h1><p>以下是简单的 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例应用程序的快速演练，它展示了 <code>cmd2</code> 的许多功能：</p><ul>
<li><a href="features/settings_plugins/">设置</a></li>
<li><a href="features/command_completion_disable/">命令</a></li>
<li><a href="features/argument_processing/">参数处理</a></li>
<li><a href="features/embedded_output_help/#%E7%94%9F%E6%88%90%E8%BE%93%E5%87%BA">生成输出</a></li>
<li><a href="features/embedded_output_help/#%E5%B8%AE%E5%8A%A9">帮助</a></li>
<li><a href="features/shortcuts_aliases_macros/#%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F">快捷方式</a></li>
<li><a href="features/history/">历史记录</a></li>
<li><a href="features/prompt_redirection/#%E5%BA%95%E9%83%A8%E5%B7%A5%E5%85%B7%E6%A0%8F">底部工具栏</a></li>
</ul><p>下面的动画演示了 <code>cat</code>、<code>echo</code> 和 <code>intro</code> 命令的实际运行效果：</p><p></p><figure><img src="https://raw.githubusercontent.com/python-cmd2/cmd2/main/docs/assets/getting-started-demo.gif" alt="getting_started 应用程序的动画演示" /><figcaption>getting_started 应用程序的动画演示</figcaption></figure><p></p><p>如果你不想边看边输入，这里是完整的源代码（你可以点击展开，然后点击右上角的 <strong>Copy</strong> 按钮）：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python3</span></div></div><div><div><div>2</div></div><div><span>"""A simple example cmd2 application demonstrating many common features.</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>Features demonstrated include all of the following:</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>1) Colorizing/stylizing output</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>2) Using multiline commands</span></div></div><div><div><div>7</div></div><div><span><span> </span></span><span>3) Persistent history</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>4) How to run an initialization script at startup</span></div></div><div><div><div>9</div></div><div><span><span> </span></span><span>5) How to group and categorize commands when displaying them in help</span></div></div><div><div><div>10</div></div><div><span><span> </span></span><span>6) Opting-in to using the ipy command to run an IPython shell</span></div></div><div><div><div>11</div></div><div><span><span> </span></span><span>7) Allowing access to your application in py and ipy</span></div></div><div><div><div>12</div></div><div><span><span> </span></span><span>8) Displaying an intro banner upon starting your application</span></div></div><div><div><div>13</div></div><div><span><span> </span></span><span>9) Using a custom prompt</span></div></div><div><div><div>14</div></div><div><span>10) How to make custom attributes settable at runtime.</span></div></div><div><div><div>15</div></div><div><span>11) Shortcuts for commands</span></div></div><div><div><div>16</div></div><div><span>12) Persistent bottom toolbar with realtime status updates</span></div></div><div><div><div>17</div></div><div><span>"""</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>import</span><span> pathlib</span></div></div><div><div><div>20</div></div><div><span>import</span><span> threading</span></div></div><div><div><div>21</div></div><div><span>import</span><span> time</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>from</span><span> prompt_toolkit.formatted_text </span><span>import</span><span> FormattedText</span></div></div><div><div><div>24</div></div><div><span>from</span><span> rich.style </span><span>import</span><span> Style</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>27</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> (</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>Color,</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>stylize,</span></div></div><div><div><div>30</div></div><div><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>class</span><span><span> </span><span>BasicApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>34</div></div><div><span>    </span><span>"""Cmd2 application to demonstrate many common features."""</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"My Custom Commands"</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>39</div></div><div><span>        </span><span>"""Initialize the cmd2 application."""</span></div></div><div><div><div>40</div></div><div><span>        </span><span># Startup script that defines a couple aliases for running shell commands</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>alias_script </span><span>=</span><span> pathlib.</span><span>Path</span><span>(</span><span>__file__</span><span><span>).</span><span>absolute</span><span>().parent </span><span>/</span><span> </span></span><span>".cmd2rc"</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>        </span><span># Create a shortcut for one of our commands</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>shortcuts </span><span>=</span><span> cmd2.</span><span>DEFAULT_SHORTCUTS</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>shortcuts.</span><span>update</span><span>({</span><span>"&amp;"</span><span>: </span><span>"intro"</span><span>})</span></div></div><div><div><div>46</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span></div></div><div><div><div>47</div></div><div><span>            </span><span>auto_suggest</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>48</div></div><div><span>            </span><span>bottom_toolbar</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>49</div></div><div><span>            </span><span>include_ipy</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>50</div></div><div><span>            </span><span>multiline_commands</span><span><span>=</span><span>[</span></span><span>"echo"</span><span>],</span></div></div><div><div><div>51</div></div><div><span>            </span><span>persistent_history_file</span><span>=</span><span>"cmd2_history.dat"</span><span>,</span></div></div><div><div><div>52</div></div><div><span>            </span><span>shortcuts</span><span><span>=</span><span>shortcuts,</span></span></div></div><div><div><div>53</div></div><div><span>            </span><span>startup_script</span><span>=</span><span>str</span><span>(alias_script),</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>        </span><span># Spawn a background thread to refresh the bottom toolbar twice a second.</span></div></div><div><div><div>57</div></div><div><span>        </span><span># This is necessary because the toolbar contains a timestamp that we want to keep current.</span></div></div><div><div><div>58</div></div><div><span>        </span><span>self</span><span><span>._stop_refresh </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>59</div></div><div><span>        </span><span>self</span><span><span>._refresh_thread </span><span>=</span><span> threading.</span><span>Thread</span><span>(</span></span><span>target</span><span>=</span><span>self</span><span>._refresh_bottom_toolbar, </span><span>daemon</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>60</div></div><div><span>        </span><span>self</span><span><span>._refresh_thread.</span><span>start</span><span>()</span></span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>        </span><span># Prints an intro banner once upon application startup</span></div></div><div><div><div>63</div></div><div><span>        </span><span>self</span><span><span>.intro </span><span>=</span><span> (</span></span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>stylize</span><span>(</span></div></div><div><div><div>65</div></div><div><span>                </span><span>"Welcome to cmd2!"</span><span>,</span></div></div><div><div><div>66</div></div><div><span>                </span><span>style</span><span><span>=</span><span>Style</span><span>(</span></span><span>color</span><span><span>=</span><span>Color.</span><span>GREEN1</span><span>, </span></span><span>bgcolor</span><span><span>=</span><span>Color.</span><span>GRAY0</span><span>, </span></span><span>bold</span><span>=</span><span>True</span><span>),</span></div></div><div><div><div>67</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>68</div></div><div><span><span>            </span></span><span>+</span><span> </span><span>" Note the full Unicode support:  😇 💩"</span></div></div><div><div><div>69</div></div><div><span><span>            </span></span><span>+</span><span> </span><span>" and the persistent bottom bar with realtime status updates!"</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>        </span><span># Show this as the prompt when asking for input</span></div></div><div><div><div>73</div></div><div><span>        </span><span>self</span><span><span>.prompt </span><span>=</span><span> </span></span><span>"myapp&gt; "</span></div></div><div><div><div>74</div></div><div>
</div></div><div><div><div>75</div></div><div><span>        </span><span># Used as prompt for multiline commands after the first line</span></div></div><div><div><div>76</div></div><div><span>        </span><span>self</span><span><span>.continuation_prompt </span><span>=</span><span> </span></span><span>"... "</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>        </span><span># Allow access to your application in py and ipy via self</span></div></div><div><div><div>79</div></div><div><span>        </span><span>self</span><span><span>.self_in_py </span><span>=</span><span> </span></span><span>True</span></div></div><div><div><div>80</div></div><div>
</div></div><div><div><div>81</div></div><div><span>        </span><span># Color to output text in with echo command</span></div></div><div><div><div>82</div></div><div><span>        </span><span>self</span><span><span>.foreground_color </span><span>=</span><span> Color.</span><span>CYAN</span><span>.value</span></span></div></div><div><div><div>83</div></div><div>
</div></div><div><div><div>84</div></div><div><span>        </span><span># Make echo_fg settable at runtime</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>fg_colors </span><span>=</span><span> [c.value </span><span>for</span><span> c </span><span>in</span><span> Color]</span></div></div><div><div><div>86</div></div><div><span>        </span><span>self</span><span><span>.</span><span>add_settable</span><span>(</span></span></div></div><div><div><div>87</div></div><div><span><span>            </span></span><span>cmd2.</span><span>Settable</span><span>(</span></div></div><div><div><div>88</div></div><div><span>                </span><span>"foreground_color"</span><span>,</span></div></div><div><div><div>89</div></div><div><span>                </span><span>str</span><span>,</span></div></div><div><div><div>90</div></div><div><span>                </span><span>"Foreground color to use with echo command"</span><span>,</span></div></div><div><div><div>91</div></div><div><span>                </span><span>self</span><span>,</span></div></div><div><div><div>92</div></div><div><span>                </span><span>choices</span><span><span>=</span><span>fg_colors,</span></span></div></div><div><div><div>93</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>95</div></div><div>
</div></div><div><div><div>96</div></div><div><span>    </span><span>def</span><span> </span><span>get_rprompt</span><span>(</span><span>self</span><span>) -&gt; </span><span>str</span><span><span> </span><span>|</span><span> FormattedText </span><span>|</span><span> </span></span><span>None</span><span>:</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>current_working_directory </span><span>=</span><span> pathlib.Path.</span><span>cwd</span><span>()</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>style </span><span>=</span><span> </span><span>"bg:ansired fg:ansiwhite"</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>text </span><span>=</span><span> </span><span>f</span><span>"cwd=</span><span>{</span><span>current_working_directory</span><span>}</span><span>"</span></div></div><div><div><div>100</div></div><div><span>        </span><span>return</span><span><span> </span><span>FormattedText</span><span>([(style, text)])</span></span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div><span>    </span><span>def</span><span> </span><span>_refresh_bottom_toolbar</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>103</div></div><div><span>        </span><span>"""Background thread target to refresh the bottom toolbar.</span></div></div><div><div><div>104</div></div><div>
</div></div><div><div><div>105</div></div><div><span><span>        </span></span><span>This is a toy example to show how the bottom toolbar can be used to display</span></div></div><div><div><div>106</div></div><div><span><span>        </span></span><span>realtime status updates in an otherwise line-oriented command interpreter.</span></div></div><div><div><div>107</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>108</div></div><div><span>        </span><span>import</span><span> contextlib</span></div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div><span>        </span><span>from</span><span> prompt_toolkit.application.current </span><span>import</span><span> get_app</span></div></div><div><div><div>111</div></div><div>
</div></div><div><div><div>112</div></div><div><span>        </span><span>while</span><span> </span><span>not</span><span> </span><span>self</span><span>._stop_refresh:</span></div></div><div><div><div>113</div></div><div><span>            </span><span>with</span><span><span> contextlib.</span><span>suppress</span><span>(</span></span><span>Exception</span><span>):</span></div></div><div><div><div>114</div></div><div><span>                </span><span># get_app() will return the currently running prompt-toolkit application</span></div></div><div><div><div>115</div></div><div><span><span>                </span></span><span>app </span><span>=</span><span> </span><span>get_app</span><span>()</span></div></div><div><div><div>116</div></div><div><span>                </span><span>if</span><span> app:</span></div></div><div><div><div>117</div></div><div><span><span>                    </span></span><span>app.</span><span>invalidate</span><span>()</span></div></div><div><div><div>118</div></div><div><span><span>            </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.5</span><span>)</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>    </span><span>def</span><span> </span><span>do_intro</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>121</div></div><div><span>        </span><span>"""Display the intro banner."""</span></div></div><div><div><div>122</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>self</span><span>.intro)</span></div></div><div><div><div>123</div></div><div>
</div></div><div><div><div>124</div></div><div><span>    </span><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>125</div></div><div><span>        </span><span>"""Multiline command."""</span></div></div><div><div><div>126</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span></div></div><div><div><div>127</div></div><div><span><span>            </span></span><span>stylize</span><span>(</span></div></div><div><div><div>128</div></div><div><span><span>                </span></span><span>arg,</span></div></div><div><div><div>129</div></div><div><span>                </span><span>style</span><span><span>=</span><span>Style</span><span>(</span></span><span>color</span><span>=</span><span>self</span><span>.foreground_color),</span></div></div><div><div><div>130</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>131</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>132</div></div><div>
</div></div><div><div><div>133</div></div><div>
</div></div><div><div><div>134</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>135</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>136</div></div><div>
</div></div><div><div><div>137</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>BasicApp</span><span>()</span></div></div><div><div><div>138</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h2>基本应用程序<a href="#基本应用程序"><span>#</span></a></h2><p>该示例将 <code>BasicApp</code> 定义为 <code>cmd2.Cmd</code> 的子类：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>BasicApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""展示许多常见功能的 Cmd2 应用程序。"""</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在文件末尾，应用程序创建该类的一个实例，并将控制权交给 <code>cmd2.Cmd.cmdloop</code> 方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>BasicApp</span><span>()</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在仓库根目录运行示例：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$</span><span> </span><span>uv</span><span> </span><span>run</span><span> </span><span>python</span><span> </span><span>examples/getting_started.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应用程序会显示其 intro 横幅和自定义的 <code>myapp&gt;</code> 提示符。由于 <code>BasicApp</code> 继承了 <code>cmd2.Cmd</code>，它也包含了 <code>cmd2</code> 的内置命令和功能。输入 <code>quit</code> 退出。</p></section><section><h2>创建设置<a href="#创建设置"><span>#</span></a></h2><p><code>cmd2</code> 对<a href="features/settings_plugins/#%E8%AE%BE%E7%BD%AE">设置</a>有强大的支持。该示例将 <code>echo</code> 命令使用的颜色保存在 <code>foreground_color</code> 属性中，然后将该属性公开为运行时设置。可选值是 <code>cmd2.Color</code> 支持的颜色值：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># echo 命令输出文本所用的颜色</span></div></div><div><div><div>2</div></div><div><span>self</span><span><span>.foreground_color </span><span>=</span><span> Color.</span><span>CYAN</span><span>.value</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 使 foreground_color 可在运行时设置</span></div></div><div><div><div>5</div></div><div><span><span>fg_colors </span><span>=</span><span> [c.value </span></span><span>for</span><span> c </span><span>in</span><span> Color]</span></div></div><div><div><div>6</div></div><div><span>self</span><span><span>.</span><span>add_settable</span><span>(</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>cmd2.</span><span>Settable</span><span>(</span></div></div><div><div><div>8</div></div><div><span>        </span><span>"foreground_color"</span><span>,</span></div></div><div><div><div>9</div></div><div><span>        </span><span>str</span><span>,</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>Text.</span><span>assemble</span><span>(</span></div></div><div><div><div>11</div></div><div><span>            </span><span>"echo 命令使用的前景色 "</span><span>,</span></div></div><div><div><div>12</div></div><div><span>            </span><span>"（选项： "</span><span>,</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>Text</span><span>(</span><span>"Green"</span><span><span>, </span><span>Style</span><span>(</span></span><span>color</span><span><span>=</span><span>Color.</span><span>GREEN</span><span>)),</span></span></div></div><div><div><div>14</div></div><div><span>            </span><span>", "</span><span>,</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>Text</span><span>(</span><span>"Red"</span><span><span>, </span><span>Style</span><span>(</span></span><span>color</span><span><span>=</span><span>Color.</span><span>RED</span><span>)),</span></span></div></div><div><div><div>16</div></div><div><span>            </span><span>", "</span><span>,</span></div></div><div><div><div>17</div></div><div><span><span>            </span></span><span>Text</span><span>(</span><span>"Blue"</span><span><span>, </span><span>Style</span><span>(</span></span><span>color</span><span><span>=</span><span>Color.</span><span>BLUE</span><span>)),</span></span></div></div><div><div><div>18</div></div><div><span>            </span><span>", ...）"</span><span>,</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>),</span></div></div><div><div><div>20</div></div><div><span>        </span><span>self</span><span>,</span></div></div><div><div><div>21</div></div><div><span>        </span><span>choices</span><span><span>=</span><span>fg_colors,</span></span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>)</span></div></div><div><div><div>23</div></div><div><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>cmd2.Cmd.add_settable</code> 方法注册了一个 <code>cmd2.utils.Settable</code>，它会根据 <code>fg_colors</code> 验证新值。使用内置的 <code>set</code> 命令查看或更改它：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>myapp</span><span>&gt; </span><span>set</span><span> </span><span>foreground_color</span></div></div><div><div><div>2</div></div><div><span>myapp</span><span>&gt; </span><span>set</span><span> </span><span>foreground_color</span><span> </span><span>red</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>第一条命令显示当前值。第二条更改后续 <code>echo</code> 输出所使用的颜色。</p></section><section><h2>命令<a href="#命令"><span>#</span></a></h2><p>以 <code>do_</code> 开头的方法会成为命令。<code>BasicApp</code> 定义了三个命令：<code>cat</code>、<code>echo</code> 和 <code>intro</code>。每个命令演示了一种不同的参数处理方式。</p><section><h3>cat<a href="#cat"><span>#</span></a></h3><p><code>cat</code> 命令使用 <code>cmd2.with_annotated</code> 根据类型注解构建其参数解析器。<code>pathlib.Path</code> 注解启用了路径补全，<code>cmd2.annotated.Option</code> 定义了可选的 <code>-n</code>/<code>--number</code> 标志：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>@cmd2</span><span>.</span><span>with_annotated</span></span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_cat</span><span>(</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>self</span><span>,</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>path</span><span>:</span><span> pathlib.Path</span><span>,</span><span>  </span><span># 必需的位置参数，带类型注解，按 Tab 自动补全文件系统路径</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>numbered</span><span>:</span><span> Annotated[  </span><span># 可选的标志参数，带类型注解、默认值和帮助文本</span></div></div><div><div><div>6</div></div><div><span>        </span><span>bool</span><span><span>, </span><span>Option</span><span>(</span></span><span>"-n"</span><span>, </span><span>"--number"</span><span><span>, </span><span>help_text</span></span><span>=</span><span>"在每行前加上其编号"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>] </span><span>=</span><span><span> </span><span>False</span></span><span>,</span></div></div><div><div><div>8</div></div><div><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"""打印文件内容。`path` 会自动以 Tab 补全文件系统路径。</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>试试：</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>cat &lt;TAB&gt;              # 路径会补全文件/目录 -- 无需额外连接补全器</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>cat notes.txt</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>cat notes.txt -n       # -n / --number，通过 Option 元数据声明</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>cat notes.txt --no-number</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>text </span><span>=</span><span> path.</span><span>read_text</span><span>()</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> text.</span><span>splitlines</span><span>()</span></div></div><div><div><div>19</div></div><div><span>    </span><span>if</span><span> numbered:</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>numbered_lines </span><span>=</span><span> []</span></div></div><div><div><div>21</div></div><div><span>        </span><span>for</span><span> index, line </span><span>in</span><span> </span><span>enumerate</span><span>(lines, </span><span>start</span><span>=</span><span>1</span><span>):</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>numbered_lines.</span><span>append</span><span>(</span><span>f</span><span>"</span><span>{</span><span>index</span><span>}</span><span>: </span><span>{</span><span>line</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>23</div></div><div><span>        </span><span>self</span><span><span>.</span><span>ppaged</span><span>(</span></span><span>"</span><span>\n</span><span>"</span><span><span>.</span><span>join</span><span>(numbered_lines))</span></span></div></div><div><div><div>24</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>25</div></div><div><span>        </span><span># 使用分页器打印内容</span></div></div><div><div><div>26</div></div><div><span>        </span><span>self</span><span><span>.</span><span>ppaged</span><span>(path.</span><span>read_text</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>该命令使用 <code>cmd2.Cmd.ppaged</code>，这样较长的文件可以在分页器中查看。在示例自带的启动脚本上试试：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>myapp</span><span>&gt; </span><span>cat</span><span> </span><span>examples/.cmd2rc</span><span> </span><span>--number</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>echo<a href="#echo"><span>#</span></a></h3><p><code>echo</code> 命令演示了 <code>cmd2.with_argparser</code>。它的解析器工厂方法定义了用于转为大写和重复输出的选项，以及一个或多个要打印的单词：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>staticmethod</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>_build_echo_parser</span><span>() -&gt; cmd2.Cmd2ArgumentParser:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"""供 echo 命令使用的解析器工厂方法。"""</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>echo_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>(</span><span>description</span><span>=</span><span>"回显输入的命令。"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>echo_parser.</span><span>add_argument</span><span>(</span><span>"-u"</span><span>, </span><span>"--upper"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"将输出转为大写"</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>echo_parser.</span><span>add_argument</span><span>(</span><span>"-r"</span><span>, </span><span>"--repeat"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>default</span><span>=</span><span>1</span><span>, </span><span>help</span><span>=</span><span>"输出 [n] 次"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>echo_parser.</span><span>add_argument</span><span>(</span><span>"words"</span><span>, </span><span>nargs</span><span>=</span><span>"+"</span><span>, </span><span>help</span><span>=</span><span>"要打印的单词"</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span>return</span><span> echo_parser</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>@cmd2</span><span>.</span><span>with_argparser</span></span><span><span>(</span><span>_build_echo_parser</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> argparse.Namespace</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>13</div></div><div><span>    </span><span>"""使用 with_argparser 装饰器解析参数的命令。"""</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>output_str </span><span>=</span><span> </span><span>" "</span><span><span>.</span><span>join</span><span>(args.words)</span></span></div></div><div><div><div>15</div></div><div><span>    </span><span>if</span><span> args.upper:</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>output_str </span><span>=</span><span> output_str.</span><span>upper</span><span>()</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(args.repeat):</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span></div></div><div><div><div>20</div></div><div><span><span>            </span></span><span>stylize</span><span>(</span></div></div><div><div><div>21</div></div><div><span><span>                </span></span><span>output_str,</span></div></div><div><div><div>22</div></div><div><span>                </span><span>style</span><span><span>=</span><span>Style</span><span>(</span></span><span>color</span><span>=</span><span>self</span><span>.foreground_color),</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>装饰器解析命令行，并将 <code>argparse.Namespace</code> 传递给 <code>do_echo()</code>。它还会根据解析器生成命令帮助。该方法使用配置的前景色为文本添加样式，并通过 <code>cmd2.Cmd.poutput</code> 写入输出，它支持 <code>cmd2</code> 的输出重定向：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>myapp</span><span>&gt; </span><span>echo</span><span> </span><span>--upper</span><span> </span><span>--repeat</span><span> </span><span>2</span><span> </span><span>hello</span><span> </span><span>cmd2</span></div></div><div><div><div>2</div></div><div><span>HELLO</span><span> </span><span>CMD2</span></div></div><div><div><div>3</div></div><div><span>HELLO</span><span> </span><span>CMD2</span></div></div><div><div><div>4</div></div><div><span>myapp</span><span>&gt; </span><span>help</span><span> </span><span>echo</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>intro<a href="#intro"><span>#</span></a></h3><p><code>intro</code> 命令不接受任何参数，因此它演示了原始的 <code>cmd2.Statement</code> 接口：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_intro</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""显示 intro 横幅。</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>此命令使用原始语句解析。总体而言，我们强烈不建议使用这种方式。但由于该命令实际上不接受任何参数，</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>在这里使用原始语句解析是安全的。</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>&amp; 键也用作此命令的快捷方式，因此你也可以输入 &amp; 来显示 intro 横幅。</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>9</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>self</span><span>.intro)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输入 <code>intro</code> 会显示应用程序启动时显示的相同横幅。</p></section></section><section><h2>快捷方式<a href="#快捷方式"><span>#</span></a></h2><p><code>cmd2</code> 有多种功能来简化重复的用户输入：<a href="features/shortcuts_aliases_macros/">快捷方式、别名和宏</a>。让我们为应用程序添加一个快捷方式。快捷方式是可以替代命令名称使用的字符串。例如，<code>cmd2</code> 支持快捷方式 <code>!</code>，它运行 <code>shell</code> 命令。所以不用输入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>Cmd</span><span>) </span><span>shell</span><span> </span><span>ls</span><span> </span><span>-al</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可以输入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>Cmd</span><span>) !</span><span>ls</span><span> </span><span>-al</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>该示例为 <code>intro</code> 命令添加了 <code>&amp;</code> 快捷方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>shortcuts </span><span>=</span><span> cmd2.</span><span>DEFAULT_SHORTCUTS</span></span></div></div><div><div><div>2</div></div><div><span><span>shortcuts.</span><span>update</span><span>({</span></span><span>"&amp;"</span><span>: </span><span>"intro"</span><span>})</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>随后，<code>shortcuts</code> 字典会与其他应用程序配置一起传递给 <code>cmd2.Cmd</code> 初始化器。以 <code>cmd2.DEFAULT_SHORTCUTS</code> 为起点可以保留内置快捷方式；调用 <code>.update()</code> 会添加新快捷方式，或覆盖具有相同键的现有快捷方式。</p><p>使用内置的 <code>shortcuts</code> 命令列出所有快捷方式，或输入 <code>&amp;</code> 来调用 <code>intro</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>myapp</span><span>&gt; </span><span>shortcuts</span></div></div><div><div><div>2</div></div><div><span>myapp</span><span>&gt; &amp;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>历史记录<a href="#历史记录"><span>#</span></a></h2><p><code>cmd2</code> 跟踪用户输入的命令历史。作为开发者，你不需要做任何事情来启用此功能，你可以免费获得它。如果你希望命令历史在应用程序调用之间持久存在，你需要做一些工作。<a href="features/history/">历史记录</a>页面有所有详细信息。</p><p>用户可以使用两种方法访问命令历史：</p><ul>
<li><a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">prompt-toolkit</a> 库，它提供了 <a href="https://en.wikipedia.org/wiki/GNU_Readline" target="_blank">GNU readline 库</a>的纯 Python 替代品，完全跨平台兼容</li>
<li><code>cmd2</code> 内置的 <code>history</code> 命令</li>
</ul><p>在基于 <code>cmd2</code> 的应用程序的提示符中，你可以按 <code>Control-p</code> 移动到先前输入的命令，按 <code>Control-n</code> 移动到下一个命令。你还可以使用 <code>Control-r</code> 搜索命令历史。</p><p>默认情况下，<code>prompt-toolkit</code> 提供 Emacs 风格的键绑定，这对 GNU Readline 库的用户来说很熟悉。你可以参考 <a href="http://readline.kablamo.org/emacs.html" target="_blank">readline 速查表</a>或深入了解 <a href="https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/key_bindings.html" target="_blank">Prompt Toolkit 用户手册</a>了解所有详细信息，包括自定义键绑定的说明。</p><p><code>history</code> 命令允许用户查看命令历史，并按编号、范围、字符串搜索或正则表达式从历史中选择命令。使用选定的命令，用户可以：</p><ul>
<li>重新运行命令</li>
<li>在文本编辑器中编辑选定的命令，并在文本编辑器退出后运行它们</li>
<li>将命令保存到文件</li>
<li>运行命令，同时将命令及其输出保存到文件</li>
</ul><p>在任何 <code>cmd2</code> 输入提示符中输入 <code>history -h</code> 了解有关 <code>history</code> 命令的更多信息，或探索<a href="features/history/#%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97">用户的命令历史</a>。</p></section><section><h2>总结<a href="#总结-1"><span>#</span></a></h2><p>你刚刚创建了一个简单但功能齐全的命令行应用程序。通过最少的工作，应用程序利用了 <code>cmd2</code> 的许多强大功能。要了解更多信息，你可以：</p><ul>
<li>深入了解 <code>cmd2</code> 提供的所有<a href="/archive/?tag=cmd2">功能特性</a></li>
<li>浏览 <a href="https://cmd2.readthedocs.io/en/latest/api/" target="_blank">API 参考</a></li>
</ul></section></section>
<section><h1>替代事件循环<a href="#替代事件循环"><span>#</span></a></h1><p>在本文档中，我们专注于 90% 的用例，我们认为这适用于超过 90% 的用户群。这侧重于易用性和最佳开箱即用体验，开发者以最少的工作量获得最多功能。我们说的是使用 <code>cmdloop()</code> 方法运行 <code>cmd2</code> 应用程序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span># 自定义属性和方法</span></div></div><div><div><div>5</div></div><div><span>    </span><span>pass</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>app </span><span>=</span><span> </span><span>App</span><span>()</span></span></div></div><div><div><div>8</div></div><div><span><span>app.</span><span>cmdloop</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然而，这种使用 <code>cmd2</code> 的方式有一些限制，主要是 <code>cmd2</code> 拥有程序的内部循环。这可能会不必要地限制使用依赖于控制自己事件循环的库。</p><p>许多 Python 并发库涉及或需要它们控制的事件循环，例如 <a href="https://docs.python.org/3/library/asyncio.html" target="_blank">asyncio</a>、<a href="http://www.gevent.org/" target="_blank">gevent</a>、<a href="https://twistedmatrix.com" target="_blank">Twisted</a> 等。</p><div><div><div></div><div>Warning</div></div><div><p>从版本 <strong>4.0</strong> 开始，<code>cmd2</code> 依赖于 <code>prompt-toolkit</code>，而后者又<a href="https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/asyncio.html" target="_blank">原生使用 asyncio</a>并启动自己的 <code>asyncio</code> 事件循环。</p><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/async_call.py" target="_blank">async_call.py</a> 示例展示了如何从 cmd2 命令调用异步函数。</p></div></div><p><code>cmd2</code> 应用程序可以通过使用以下代码以 <code>cmd2</code> 不拥有程序主循环的方式执行：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>Cmd2EventBased</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>cmd2.Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span># ... 你的类代码 ...</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>Cmd2EventBased</span><span>()</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>app.</span><span>preloop</span><span>()</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span># 在你希望运行单个命令的任何事件循环机制中执行此操作</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>cmd_line_text </span><span>=</span><span> </span><span>"help history"</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>app.</span><span>runcmds_plus_hooks</span><span>([cmd_line_text])</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>app.</span><span>postloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>cmd2.Cmd.runcmds_plus_hooks</code> 方法运行多个命令，其中每个单独的命令通过 <code>cmd2.Cmd.onecmd_plus_hooks</code> 执行。</p><p><code>cmd2.Cmd.onecmd_plus_hooks</code> 方法将执行以下操作来以正常方式执行单个命令：</p><ol>
<li>将用户输入解析为 <code>cmd2.Statement</code> 对象</li>
<li>调用使用 <code>cmd2.Cmd.register_postparsing_hook</code> 注册的方法</li>
<li>如果用户请求且允许，重定向输出</li>
<li>启动计时器</li>
<li>调用使用 <code>cmd2.Cmd.register_precmd_hook</code> 注册的方法</li>
<li>调用 <code>cmd2.Cmd.precmd</code> - 为了与 <code>cmd</code> 向后兼容</li>
<li>将语句添加到<a href="features/history/">历史记录</a></li>
<li>调用 <code>do_command</code> 方法</li>
<li>调用使用 <code>cmd2.Cmd.register_postcmd_hook</code> 注册的方法</li>
<li>调用 <code>cmd2.Cmd.postcmd</code> - 为了与 <code>cmd</code> 向后兼容</li>
<li>停止计时器并显示经过的时间</li>
<li>如果输出被重定向，停止重定向输出</li>
<li>调用使用 <code>cmd2.Cmd.register_cmdfinalization_hook</code> 注册的方法</li>
</ol><p>以这种方式运行可以实现与外部事件循环的集成。然而，如何与任何特定事件循环集成超出了本文档的范围。请注意，以这种方式运行有几个缺点，包括：</p><ul>
<li>要求开发者编写更多代码</li>
<li>不允许通过命令行参数在调用时运行命令</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/app_setup/</id>
      <title type="text">Python cmd2：应用初始化、多行命令与启动命令</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/app_setup/"/>
      <summary type="text">介绍 cmd2 应用的初始化配置、类变量与实例属性、多行命令的使用，以及启动时执行命令的多种方式。</summary>
      <content type="html"><![CDATA[<section><h1>应用初始化<a href="#应用初始化"><span>#</span></a></h1><p>以下是一个基本的 <code>cmd2</code> 应用示例，演示了你在初始化应用时可能希望利用的许多功能：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python3</span></div></div><div><div><div>2</div></div><div><span>"""A simple example cmd2 application demonstrating many common features.</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>Features demonstrated include all of the following:</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>1) Colorizing/stylizing output</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>2) Using multiline commands</span></div></div><div><div><div>7</div></div><div><span><span> </span></span><span>3) Persistent history</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>4) How to run an initialization script at startup</span></div></div><div><div><div>9</div></div><div><span><span> </span></span><span>5) How to group and categorize commands when displaying them in help</span></div></div><div><div><div>10</div></div><div><span><span> </span></span><span>6) Opting-in to using the ipy command to run an IPython shell</span></div></div><div><div><div>11</div></div><div><span><span> </span></span><span>7) Allowing access to your application in py and ipy</span></div></div><div><div><div>12</div></div><div><span><span> </span></span><span>8) Displaying an intro banner upon starting your application</span></div></div><div><div><div>13</div></div><div><span><span> </span></span><span>9) Using a custom prompt</span></div></div><div><div><div>14</div></div><div><span>10) How to make custom attributes settable at runtime.</span></div></div><div><div><div>15</div></div><div><span>11) Shortcuts for commands</span></div></div><div><div><div>16</div></div><div><span>12) Persistent bottom toolbar with realtime status updates</span></div></div><div><div><div>17</div></div><div><span>"""</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>import</span><span> pathlib</span></div></div><div><div><div>20</div></div><div><span>import</span><span> threading</span></div></div><div><div><div>21</div></div><div><span>import</span><span> time</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>from</span><span> prompt_toolkit.formatted_text </span><span>import</span><span> FormattedText</span></div></div><div><div><div>24</div></div><div><span>from</span><span> rich.style </span><span>import</span><span> Style</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>27</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> (</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>Color,</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>stylize,</span></div></div><div><div><div>30</div></div><div><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>class</span><span><span> </span><span>BasicApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>34</div></div><div><span>    </span><span>"""Cmd2 application to demonstrate many common features."""</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"My Custom Commands"</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>39</div></div><div><span>        </span><span>"""Initialize the cmd2 application."""</span></div></div><div><div><div>40</div></div><div><span>        </span><span># Startup script that defines a couple aliases for running shell commands</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>alias_script </span><span>=</span><span> pathlib.</span><span>Path</span><span>(</span><span>__file__</span><span><span>).</span><span>absolute</span><span>().parent </span><span>/</span><span> </span></span><span>".cmd2rc"</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>        </span><span># Create a shortcut for one of our commands</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>shortcuts </span><span>=</span><span> cmd2.</span><span>DEFAULT_SHORTCUTS</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>shortcuts.</span><span>update</span><span>({</span><span>"&amp;"</span><span>: </span><span>"intro"</span><span>})</span></div></div><div><div><div>46</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span></div></div><div><div><div>47</div></div><div><span>            </span><span>auto_suggest</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>48</div></div><div><span>            </span><span>bottom_toolbar</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>49</div></div><div><span>            </span><span>include_ipy</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>50</div></div><div><span>            </span><span>multiline_commands</span><span><span>=</span><span>[</span></span><span>"echo"</span><span>],</span></div></div><div><div><div>51</div></div><div><span>            </span><span>persistent_history_file</span><span>=</span><span>"cmd2_history.dat"</span><span>,</span></div></div><div><div><div>52</div></div><div><span>            </span><span>shortcuts</span><span><span>=</span><span>shortcuts,</span></span></div></div><div><div><div>53</div></div><div><span>            </span><span>startup_script</span><span>=</span><span>str</span><span>(alias_script),</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>        </span><span># Spawn a background thread to refresh the bottom toolbar twice a second.</span></div></div><div><div><div>57</div></div><div><span>        </span><span># This is necessary because the toolbar contains a timestamp that we want to keep current.</span></div></div><div><div><div>58</div></div><div><span>        </span><span>self</span><span><span>._stop_refresh </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>59</div></div><div><span>        </span><span>self</span><span><span>._refresh_thread </span><span>=</span><span> threading.</span><span>Thread</span><span>(</span></span><span>target</span><span>=</span><span>self</span><span>._refresh_bottom_toolbar, </span><span>daemon</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>60</div></div><div><span>        </span><span>self</span><span><span>._refresh_thread.</span><span>start</span><span>()</span></span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>        </span><span># Prints an intro banner once upon application startup</span></div></div><div><div><div>63</div></div><div><span>        </span><span>self</span><span><span>.intro </span><span>=</span><span> (</span></span></div></div><div><div><div>64</div></div><div><span><span>            </span></span><span>stylize</span><span>(</span></div></div><div><div><div>65</div></div><div><span>                </span><span>"Welcome to cmd2!"</span><span>,</span></div></div><div><div><div>66</div></div><div><span>                </span><span>style</span><span><span>=</span><span>Style</span><span>(</span></span><span>color</span><span><span>=</span><span>Color.</span><span>GREEN1</span><span>, </span></span><span>bgcolor</span><span><span>=</span><span>Color.</span><span>GRAY0</span><span>, </span></span><span>bold</span><span>=</span><span>True</span><span>),</span></div></div><div><div><div>67</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>68</div></div><div><span><span>            </span></span><span>+</span><span> </span><span>" Note the full Unicode support:  😇 💩"</span></div></div><div><div><div>69</div></div><div><span><span>            </span></span><span>+</span><span> </span><span>" and the persistent bottom bar with realtime status updates!"</span></div></div><div><div><div>70</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>        </span><span># Show this as the prompt when asking for input</span></div></div><div><div><div>73</div></div><div><span>        </span><span>self</span><span><span>.prompt </span><span>=</span><span> </span></span><span>"myapp&gt; "</span></div></div><div><div><div>74</div></div><div>
</div></div><div><div><div>75</div></div><div><span>        </span><span># Used as prompt for multiline commands after the first line</span></div></div><div><div><div>76</div></div><div><span>        </span><span>self</span><span><span>.continuation_prompt </span><span>=</span><span> </span></span><span>"... "</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>        </span><span># Allow access to your application in py and ipy via self</span></div></div><div><div><div>79</div></div><div><span>        </span><span>self</span><span><span>.self_in_py </span><span>=</span><span> </span></span><span>True</span></div></div><div><div><div>80</div></div><div>
</div></div><div><div><div>81</div></div><div><span>        </span><span># Color to output text in with echo command</span></div></div><div><div><div>82</div></div><div><span>        </span><span>self</span><span><span>.foreground_color </span><span>=</span><span> Color.</span><span>CYAN</span><span>.value</span></span></div></div><div><div><div>83</div></div><div>
</div></div><div><div><div>84</div></div><div><span>        </span><span># Make echo_fg settable at runtime</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>fg_colors </span><span>=</span><span> [c.value </span><span>for</span><span> c </span><span>in</span><span> Color]</span></div></div><div><div><div>86</div></div><div><span>        </span><span>self</span><span><span>.</span><span>add_settable</span><span>(</span></span></div></div><div><div><div>87</div></div><div><span><span>            </span></span><span>cmd2.</span><span>Settable</span><span>(</span></div></div><div><div><div>88</div></div><div><span>                </span><span>"foreground_color"</span><span>,</span></div></div><div><div><div>89</div></div><div><span>                </span><span>str</span><span>,</span></div></div><div><div><div>90</div></div><div><span>                </span><span>"Foreground color to use with echo command"</span><span>,</span></div></div><div><div><div>91</div></div><div><span>                </span><span>self</span><span>,</span></div></div><div><div><div>92</div></div><div><span>                </span><span>choices</span><span><span>=</span><span>fg_colors,</span></span></div></div><div><div><div>93</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>95</div></div><div>
</div></div><div><div><div>96</div></div><div><span>    </span><span>def</span><span> </span><span>get_rprompt</span><span>(</span><span>self</span><span>) -&gt; </span><span>str</span><span><span> </span><span>|</span><span> FormattedText </span><span>|</span><span> </span></span><span>None</span><span>:</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>current_working_directory </span><span>=</span><span> pathlib.Path.</span><span>cwd</span><span>()</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>style </span><span>=</span><span> </span><span>"bg:ansired fg:ansiwhite"</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>text </span><span>=</span><span> </span><span>f</span><span>"cwd=</span><span>{</span><span>current_working_directory</span><span>}</span><span>"</span></div></div><div><div><div>100</div></div><div><span>        </span><span>return</span><span><span> </span><span>FormattedText</span><span>([(style, text)])</span></span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div><span>    </span><span>def</span><span> </span><span>_refresh_bottom_toolbar</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>103</div></div><div><span>        </span><span>"""Background thread target to refresh the bottom toolbar.</span></div></div><div><div><div>104</div></div><div>
</div></div><div><div><div>105</div></div><div><span><span>        </span></span><span>This is a toy example to show how the bottom toolbar can be used to display</span></div></div><div><div><div>106</div></div><div><span><span>        </span></span><span>realtime status updates in an otherwise line-oriented command interpreter.</span></div></div><div><div><div>107</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>108</div></div><div><span>        </span><span>import</span><span> contextlib</span></div></div><div><div><div>109</div></div><div>
</div></div><div><div><div>110</div></div><div><span>        </span><span>from</span><span> prompt_toolkit.application.current </span><span>import</span><span> get_app</span></div></div><div><div><div>111</div></div><div>
</div></div><div><div><div>112</div></div><div><span>        </span><span>while</span><span> </span><span>not</span><span> </span><span>self</span><span>._stop_refresh:</span></div></div><div><div><div>113</div></div><div><span>            </span><span>with</span><span><span> contextlib.</span><span>suppress</span><span>(</span></span><span>Exception</span><span>):</span></div></div><div><div><div>114</div></div><div><span>                </span><span># get_app() will return the currently running prompt-toolkit application</span></div></div><div><div><div>115</div></div><div><span><span>                </span></span><span>app </span><span>=</span><span> </span><span>get_app</span><span>()</span></div></div><div><div><div>116</div></div><div><span>                </span><span>if</span><span> app:</span></div></div><div><div><div>117</div></div><div><span><span>                    </span></span><span>app.</span><span>invalidate</span><span>()</span></div></div><div><div><div>118</div></div><div><span><span>            </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.5</span><span>)</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>    </span><span>def</span><span> </span><span>do_intro</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>121</div></div><div><span>        </span><span>"""Display the intro banner."""</span></div></div><div><div><div>122</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>self</span><span>.intro)</span></div></div><div><div><div>123</div></div><div>
</div></div><div><div><div>124</div></div><div><span>    </span><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>125</div></div><div><span>        </span><span>"""Multiline command."""</span></div></div><div><div><div>126</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span></div></div><div><div><div>127</div></div><div><span><span>            </span></span><span>stylize</span><span>(</span></div></div><div><div><div>128</div></div><div><span><span>                </span></span><span>arg,</span></div></div><div><div><div>129</div></div><div><span>                </span><span>style</span><span><span>=</span><span>Style</span><span>(</span></span><span>color</span><span>=</span><span>self</span><span>.foreground_color),</span></div></div><div><div><div>130</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>131</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>132</div></div><div>
</div></div><div><div><div>133</div></div><div>
</div></div><div><div><div>134</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>135</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>136</div></div><div>
</div></div><div><div><div>137</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>BasicApp</span><span>()</span></div></div><div><div><div>138</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h2>Cmd 类初始化器<a href="#cmd-类初始化器"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 实例或子类实例是一个交互式 CLI 应用框架。没有充分的理由直接实例化 <code>Cmd</code>；相反，它作为你自定义类的超类来继承 <code>Cmd</code> 的方法并封装操作方法。</p><p>某些内容必须在你从 <code>cmd2.Cmd</code> 派生的类的 <code>__init__()</code> 方法中初始化（<code>__init__()</code> 的所有参数都是可选的）。</p></section><section><h2>Cmd 类变量<a href="#cmd-类变量"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 类提供了几个类级别的变量，可以在子类中被重写以更改该类所有实例的默认行为。</p><ul>
<li><strong>DEFAULT_CATEGORY</strong>：尚未显式分类的命令的默认帮助分类。（默认：<code>"Cmd2 Commands"</code>）</li>
<li><strong>DEFAULT_EDITOR</strong>：<code>edit</code> 命令使用的默认编辑器程序。</li>
<li><strong>DEFAULT_PROMPT</strong>：默认提示符字符串。（默认：<code>"(Cmd) "</code>）</li>
<li><strong>MISC_HEADER</strong>：列出杂项帮助主题的帮助部分标题。（默认：<code>"Miscellaneous Help Topics"</code>）</li>
</ul></section><section><h2>Cmd 实例属性<a href="#cmd-实例属性"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 类提供了大量公共实例属性，允许开发者在 <code>__init__()</code> 方法提供的选项之外进一步自定义 <code>cmd2</code> 应用。</p><section><h3>公共实例属性<a href="#公共实例属性"><span>#</span></a></h3><p>以下是开发者可能希望重写的 <code>cmd2.Cmd</code> 实例属性：</p><ul>
<li><strong>broken_pipe_warning</strong>：如果非空，当发生管道断裂错误时将显示此字符串</li>
<li><strong>continuation_prompt</strong>：多行命令在第 2 行及后续行输入时使用的提示符</li>
<li><strong>debug</strong>：如果为 <code>True</code>，在错误时显示完整堆栈跟踪（默认：<code>False</code>）</li>
<li><strong>default_error</strong>：运行不存在的命令时打印的错误信息</li>
<li><strong>disabled_commands</strong>：已被禁用的命令。用于支持仅在应用特定状态下可用的命令。此字典的键是命令名称，值是 DisabledCommand 对象</li>
<li><strong>echo</strong>：如果为 <code>True</code>，用户发出的每个命令在执行前会被回显到屏幕上。这在运行脚本时特别有用。在提示符处运行命令时不会出现此行为。（默认：<code>False</code>）</li>
<li><strong>editor</strong>：与 <em>edit</em> 命令一起使用的文本编辑器程序（如 <code>vim</code>）</li>
<li><strong>exclude_from_history</strong>：从 <em>history</em> 命令中排除的命令</li>
<li><strong>exit_code</strong>：决定退出应用时 <code>cmdloop()</code> 返回的值</li>
<li><strong>help_error</strong>：找不到帮助信息时打印的错误信息</li>
<li><strong>hidden_commands</strong>：从帮助菜单和 Tab 补全中排除的命令</li>
<li><strong>last_result</strong>：存储上一个命令运行的结果，以便在 Python 脚本或交互式控制台中使用结果。内置命令不使用此属性，它纯粹为用户定义的命令和便利而设</li>
<li><strong>macros</strong>：宏名称及其值的字典</li>
<li><strong>max_column_completion_results</strong>：单列中显示的最大补全结果数（默认：7）</li>
<li><strong>max_completion_table_items</strong>：显示补全表允许的最大补全结果数（默认：50）</li>
<li><strong>pager</strong>：设置 <code>Cmd.ppaged()</code> 方法用于使用分页器显示换行输出的分页器命令</li>
<li><strong>pager_chop</strong>：设置 <code>Cmd.ppaged()</code> 方法用于使用分页器显示截断输出的分页器命令</li>
<li><strong>py_bridge_name</strong>：嵌入式 Python 环境和脚本用来调用命令的 <code>cmd2</code> 应用名称（默认：<code>app</code>）</li>
<li><strong>py_locals</strong>：定义 Python shell 和脚本中可用的特定变量/函数的字典（比使用 <strong>self_in_py</strong> 使所有内容可用提供更精细的控制）</li>
<li><strong>quiet</strong>：如果为 <code>True</code>，完全抑制非必要输出（默认：<code>False</code>）</li>
<li><strong>scripts_add_to_history</strong>：如果为 <code>True</code>，脚本和 pyscripts 会将命令添加到历史记录（默认：<code>True</code>）</li>
<li><strong>self_in_py</strong>：如果为 <code>True</code>，允许在 <em>py</em> 命令中通过 <code>self</code> 访问你的应用（默认：<code>False</code>）</li>
<li><strong>settable</strong>：控制哪些实例属性可以在运行时使用 <em>set</em> 命令设置的字典</li>
<li><strong>timing</strong>：如果为 <code>True</code>，显示每个命令的执行时间（默认：<code>False</code>）</li>
</ul></section></section></section>
<section><h1>多行命令<a href="#多行命令"><span>#</span></a></h1><p>对于名称列在 <code>cmd2.Cmd.__init__</code> 的 <code>multiline_commands</code> 参数中的命令，命令输入可以跨越多行。这些命令只有在用户输入了_终止符_后才会被执行。默认情况下，命令终止符是 <code>;</code>。通过指定 <code>cmd2.Cmd.__init__()</code> 的 <code>terminators</code> 可选参数可以设置不同的终止符。空行_始终_被视为命令终止符（不可被重写）。</p><p>在多行命令中，输出重定向字符如 <code>&gt;</code> 和 <code>|</code> 是命令参数的一部分，除非它们出现在终止符之后。</p><section><h2>续行提示符<a href="#续行提示符"><span>#</span></a></h2><p>当用户输入<strong>多行命令</strong>时，它可能跨越多行输入。第一行输入的提示符由 <code>cmd2.Cmd.prompt</code> 实例属性指定——详见<a href="../prompt_redirection/#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8F%90%E7%A4%BA%E7%AC%A6">自定义提示符</a>。后续行输入的提示符由 <code>cmd2.Cmd.continuation_prompt</code> 属性定义。</p></section><section><h2>使用场景<a href="#使用场景"><span>#</span></a></h2><p>多行命令应该谨慎使用，以保持基于 <code>cmd2</code> 的面向行的命令解释器应用的良好用户体验。</p><p>然而，某些使用场景从跨越多行的命令能力中受益匪浅。例如，你可能希望用户能够输入 SQL 命令，这些命令通常跨越多行并以分号终止。</p><p>我们估计不到 5% 的 <code>cmd2</code> 应用使用此功能。但它在这里为那些能从中获得价值的使用场景而准备。</p></section></section>
<section><h1>启动命令<a href="#启动命令"><span>#</span></a></h1><p><code>cmd2</code> 提供了几种在应用启动后立即运行命令的方式：</p><ol>
<li>调用时传入命令</li>
<li>启动脚本</li>
</ol><p>作为启动脚本一部分运行的命令总是在应用完成初始化后立即运行，因此它们保证在任何_调用时命令_之前运行。</p><section><h2>调用时传入命令<a href="#调用时传入命令"><span>#</span></a></h2><p>默认情况下，你可以在调用应用时通过将命令作为额外参数传递给程序来向应用发送命令。<code>cmd2</code> 将每个参数解释为一个单独的命令，因此如果命令超过一个单词，你应该将每个命令用引号括起来。你可以使用单引号或双引号。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ uv run examples/cmd_as_argument.py "say hello" "say Gracie" quit</span></div></div><div><div><div>2</div></div><div><span>hello</span></div></div><div><div><div>3</div></div><div><span>Gracie</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可以在命令末尾加上 <strong>quit</strong> 命令，这样你的 <code>cmd2</code> 应用就会像非交互式命令行工具（CLU）一样运行。这意味着它可以被外部应用程序脚本化，并轻松用于自动化。</p><div><div><div></div><div>Note</div></div><div><p>如果你希望禁用 cmd2 对命令行参数的消费，可以通过将 <code>cmd2.Cmd</code> 类实例的 <code>allow_cli_args</code> 参数设为 <code>False</code> 来实现。例如，如果你想使用 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a> 来解析应用的整体命令行参数时，这会很有用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span><span>allow_cli_args</span><span>=</span><span>False</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div><div><div><div></div><div>Tip</div></div><div><p>如果你希望使用类似 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a> 的工具来解析应用的整体命令行参数，但仍希望能将任何额外参数作为命令传递给你的应用，请参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py" target="_blank">argparse_example.py</a> 示例末尾的 <code>if __name__ == "__main__":</code> 代码块。</p><p>可以这样运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>uv</span><span> </span><span>run</span><span> </span><span>examples/argparse_example.py</span><span> </span><span>-c</span><span> </span><span>blue</span><span> </span><span>help</span><span> </span><span>quit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div></section><section><h2>启动脚本<a href="#启动脚本"><span>#</span></a></h2><p>你可以通过将文件路径传递给 <code>cmd2.Cmd.__init__()</code> 方法的 <code>startup_script</code> 参数来从初始化脚本执行命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>StartupApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>cmd2.Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>, </span><span>startup_script</span><span>=</span><span>".cmd2rc"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此文本文件应包含一个<a href="../scripting/#%E5%91%BD%E4%BB%A4%E8%84%9A%E6%9C%AC">命令脚本</a>。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例了解演示。</p><p>你可以通过将 <code>silence_startup_script</code> 设为 True 来静音启动脚本的输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cmd2.Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>, </span><span>startup_script</span><span>=</span><span>".cmd2rc"</span><span>, </span><span>silence_startup_script</span><span>=</span><span>True</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>写入 <code>stderr</code> 的内容仍会为”静音”的启动脚本打印。此外，如果 <code>allow_redirection</code> 为 False，则启动脚本无法被静音，因为静音功能通过将脚本输出重定向到 <code>os.devnull</code> 来实现。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/argument_processing/</id>
      <title type="text">Python cmd2：参数处理详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/argument_processing/"/>
      <summary type="text">详细介绍 cmd2 的参数处理功能，包括使用 argparse 装饰器、参数解析、帮助消息、子命令等高级特性。</summary>
      <content type="html"><![CDATA[<section><h1>参数处理<a href="#参数处理"><span>#</span></a></h1><p><code>cmd2</code> 使得使用 Python 的 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a> 模块为命令添加复杂的参数处理变得非常简单。<code>cmd2</code> 会为你处理以下事项：</p><ol>
<li>以类似 POSIX shell 的方式解析输入和带引号的字符串</li>
<li>使用你提供的 <a href="https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser" target="_blank">Cmd2ArgumentParser</a> 实例解析参数列表</li>
<li>将解析后的 <code>argparse.Namespace</code> 对象传递给你的命令函数。<code>Namespace</code> 中包含解析命令行时创建的 <code>cmd2.Statement</code> 对象，可通过 <code>Namespace</code> 的 <code>cmd2_statement</code> 属性访问</li>
<li>将参数解析器的用法信息添加到命令的帮助文档中</li>
<li>检查是否存在 <code>-h/--help</code> 选项，如果存在则显示命令的帮助信息</li>
</ol><p>这些功能由两个装饰器提供：</p><ul>
<li><code>@with_argparser</code> — 通过手动调用 <code>add_argument()</code> 构建解析器</li>
<li><code>@with_annotated</code> — 根据类型注解自动构建解析器</li>
</ul><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_completion.py" target="_blank">argparse_completion</a> 和 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/annotated_example.py" target="_blank">annotated_example</a> 示例，并排比较这两种风格。</p><p><code>cmd2</code> 提供了以下装饰器来辅助解析传递给命令的参数：</p><ul>
<li><code>cmd2.decorators.with_argparser</code></li>
<li><code>cmd2.annotated.with_annotated</code></li>
<li><code>cmd2.decorators.with_argument_list</code></li>
</ul><p>所有这些装饰器都接受一个可选的 <strong>preserve_quotes</strong> 参数，默认值为 <code>False</code>。将此参数设为 <code>True</code> 在你需要将参数传递给另一个可能有自己参数解析逻辑的命令时非常有用。</p><section><h2>with_argparser 装饰器<a href="#with_argparser-装饰器"><span>#</span></a></h2><p><code>@with_argparser</code> 装饰器的第一个参数可以接受以下类型：</p><ol>
<li>一个现有的 <code>Cmd2ArgumentParser</code> 实例</li>
<li>一个返回 <code>Cmd2ArgumentParser</code> 实例的函数或静态方法</li>
<li>一个返回 <code>Cmd2ArgumentParser</code> 实例的 Cmd 或 CommandSet 类方法</li>
</ol><p>在所有情况下，<code>@with_argparser</code> 装饰器都会创建解析器实例的深拷贝并存储在内部。这意味着解析器不需要在不同命令之间保持唯一。</p><div><div><div></div><div>Warning</div></div><div><p>由于 <code>@with_argparser</code> 装饰器会对提供的解析器进行深拷贝，如果你希望稍后动态修改该解析器，你需要获取这个深拷贝。可以通过 <code>self.command_parsers.get(self.do_commandname)</code> 来实现。</p></div></div></section><section><h2>with_annotated 装饰器<a href="#with_annotated-装饰器"><span>#</span></a></h2><div><div><div></div><div>Warning</div></div><div><p><code>@with_annotated</code> 装饰器是<strong>实验性</strong>的，其 API 可能在未来版本中发生变化。</p></div></div><p><code>@with_annotated</code> 装饰器会根据被装饰函数的类型注解自动构建 argparse 解析器——无需手动调用 <code>add_argument()</code>。完整参考请参阅 <a href="../annotated/">Annotated 参数处理</a>，包括类型映射、元数据类、子命令和稳定性方面的注意事项。</p></section><section><h2>参数解析<a href="#参数解析"><span>#</span></a></h2><p>对于 <code>cmd2.Cmd</code> 子类中每个需要参数解析的命令，创建一个 <code>Cmd2ArgumentParser</code> 实例来适当地解析该命令的输入（或提供一个返回此类解析器的函数/方法）。然后用 <code>@with_argparser</code> 装饰器装饰命令方法，将参数解析器作为装饰器的第一个参数传递。这会改变命令方法的第二个参数，该参数将包含 <code>Cmd2ArgumentParser.parse_args()</code> 的结果。</p><p>以下是具体示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd2ArgumentParser, with_argparser</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>argparser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>()</span></span></div></div><div><div><div>4</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"-p"</span><span>, </span><span>"--piglatin"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"atinLay"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"-s"</span><span>, </span><span>"--shout"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"N00B EMULATION MODE"</span><span>)</span></div></div><div><div><div>6</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"-r"</span><span>, </span><span>"--repeat"</span><span>, </span><span>type</span><span>=</span><span>int</span><span>, </span><span>help</span><span>=</span><span>"output [n] times"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"word"</span><span>, </span><span>nargs</span><span>=</span><span>"?"</span><span>, </span><span>help</span><span>=</span><span>"word to say"</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>@with_argparser</span><span><span>(</span><span>argparser</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>do_speak</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>opts</span></span><span>):</span></div></div><div><div><div>12</div></div><div><span>    </span><span>"""Repeats what you tell me to."""</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>arg </span><span>=</span><span> opts.word</span></div></div><div><div><div>14</div></div><div><span>    </span><span>if</span><span> opts.piglatin:</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>arg </span><span>=</span><span> </span><span>"</span><span>%s%s</span><span>ay"</span><span><span> </span><span>%</span><span> (arg[</span></span><span>1</span><span>:], arg[</span><span>0</span><span>])</span></div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> opts.shout:</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>arg </span><span>=</span><span> arg.</span><span>upper</span><span>()</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>repetitions </span><span>=</span><span> opts.repeat </span><span>or</span><span> </span><span>1</span></div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>min</span><span>(repetitions, </span><span>self</span><span>.maxrepeats)):</span></div></div><div><div><div>20</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(arg)</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div></div><div>Note</div></div><div><p><code>cmd2</code> 会根据被装饰方法的名称设置参数解析器中的 <code>prog</code> 变量。这会覆盖你在创建参数解析器时指定的任何 <code>prog</code> 值。</p><p>从 3.0.0 版本开始，<code>cmd2</code> 在创建实例特定的解析器时设置 <code>prog</code>，这比之前的版本要晚。</p></div></div></section><section><h2>帮助消息<a href="#帮助消息"><span>#</span></a></h2><p>默认情况下，当用户请求某个命令的帮助时，<code>cmd2</code> 使用命令方法的文档字符串。当你使用 <code>@with_argparser</code> 装饰器时，<code>do_*</code> 方法的文档字符串会被用来设置 <code>Cmd2ArgumentParser</code> 的 description。</p><div><div><div></div><div>Tip</div></div><div><p>虽然 <code>help</code> 字段只是一个简单的字符串，但 <code>description</code> 和 <code>epilog</code> 字段可以接受任何 <a href="https://github.com/Textualize/rich" target="_blank">rich</a> 可渲染对象。这允许你使用 rich 的所有内置对象，如 <code>Text</code>、<code>Table</code> 和 <code>Markdown</code>。</p></div></div><p>使用以下代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd2ArgumentParser, with_argparser</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>argparser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>()</span></span></div></div><div><div><div>4</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"tag"</span><span>, </span><span>help</span><span>=</span><span>"tag"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"content"</span><span>, </span><span>nargs</span><span>=</span><span>"+"</span><span>, </span><span>help</span><span>=</span><span>"content to surround with tag"</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>@with_argparser</span><span><span>(</span><span>argparser</span><span>)</span></span></div></div><div><div><div>9</div></div><div><span>def</span><span> </span><span>do_tag</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>10</div></div><div><span>    </span><span>"""Create an HTML tag"""</span></div></div><div><div><div>11</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"&lt;</span><span>{0}</span><span>&gt;</span><span>{1}</span><span>&lt;/</span><span>{0}</span><span>&gt;"</span><span><span>.</span><span>format</span><span>(args.tag, </span></span><span>" "</span><span><span>.</span><span>join</span><span>(args.content)))</span></span></div></div><div><div><div>12</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"</span><span>\n</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>help tag</code> 命令将显示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>usage: tag [-h] tag content [content ...]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Create an HTML tag</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>positional arguments:</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>tag         tag</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>content     content to surround with tag</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>optional arguments:</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>-h, --help  show this help message and exit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你愿意，可以在实例化 <code>Cmd2ArgumentParser</code> 时设置 <code>description</code>，并留空方法的文档字符串：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd2ArgumentParser, with_argparser</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>argparser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>(</span></span><span>description</span><span>=</span><span>"create an HTML tag"</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"tag"</span><span>, </span><span>help</span><span>=</span><span>"tag"</span><span>)</span></div></div><div><div><div>5</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"content"</span><span>, </span><span>nargs</span><span>=</span><span>"+"</span><span>, </span><span>help</span><span>=</span><span>"content to surround with tag"</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>@with_argparser</span><span><span>(</span><span>argparser</span><span>)</span></span></div></div><div><div><div>9</div></div><div><span>def</span><span> </span><span>do_tag</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>10</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"&lt;</span><span>{0}</span><span>&gt;</span><span>{1}</span><span>&lt;/</span><span>{0}</span><span>&gt;"</span><span><span>.</span><span>format</span><span>(args.tag, </span></span><span>" "</span><span><span>.</span><span>join</span><span>(args.content)))</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"</span><span>\n</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>现在当用户输入 <code>help tag</code> 时会看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>usage: tag [-h] tag content [content ...]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>create an HTML tag</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>positional arguments:</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>tag         tag</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>content     content to surround with tag</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>optional arguments:</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>-h, --help  show this help message and exit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要在生成的帮助消息末尾添加额外文本，可以使用 <code>epilog</code> 变量：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd2ArgumentParser, with_argparser</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>argparser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>(</span></span></div></div><div><div><div>4</div></div><div><span>    </span><span>description</span><span>=</span><span>"create an HTML tag"</span><span>, </span><span>epilog</span><span>=</span><span>"This command cannot generate tags with no content, like &lt;br/&gt;."</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"tag"</span><span>, </span><span>help</span><span>=</span><span>"tag"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>argparser.</span><span>add_argument</span><span>(</span></span><span>"content"</span><span>, </span><span>nargs</span><span>=</span><span>"+"</span><span>, </span><span>help</span><span>=</span><span>"content to surround with tag"</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>@with_argparser</span><span><span>(</span><span>argparser</span><span>)</span></span></div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>do_tag</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>12</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"&lt;</span><span>{0}</span><span>&gt;</span><span>{1}</span><span>&lt;/</span><span>{0}</span><span>&gt;"</span><span><span>.</span><span>format</span><span>(args.tag, </span></span><span>" "</span><span><span>.</span><span>join</span><span>(args.content)))</span></span></div></div><div><div><div>13</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"</span><span>\n</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>usage: tag [-h] tag content [content ...]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>create an HTML tag</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>positional arguments:</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>tag         tag</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>content     content to surround with tag</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>optional arguments:</span></div></div><div><div><div>10</div></div><div><span><span>  </span></span><span>-h, --help  show this help message and exit</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>This command cannot generate tags with no content, like &lt;br/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>如果命令 <strong>foo</strong> 使用了 <code>cmd2</code> 的 <code>with_argparse</code> 装饰器，那么当调用 <code>help foo</code> 时，<strong>help_foo</strong> 将不会被调用。<a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse</a> 模块提供了一个丰富的 API，可以用来调整显示帮助的各个方面，我们鼓励 <code>cmd2</code> 开发者利用它。</p></div></div><section><h3>Argparse HelpFormatter 类<a href="#argparse-helpformatter-类"><span>#</span></a></h3><p><code>cmd2</code> 提供了 5 种不同的 Argparse HelpFormatter 类，它们都基于 <a href="https://github.com/hamdanal/rich-argparse" target="_blank">rich-argparse</a> 的 <code>RichHelpFormatter</code> 类。好处是你的 <code>cmd2</code> 应用现在拥有了更美观的帮助信息，包含颜色使其更容易快速阅读。这对所有支持的 Python 版本都适用。</p><ul>
<li><code>Cmd2HelpFormatter</code> - 默认的帮助格式化类</li>
<li><code>ArgumentDefaultsCmd2HelpFormatter</code> - 在参数帮助中添加默认值</li>
<li><code>MetavarTypeCmd2HelpFormatter</code> - 使用参数的 <code>type</code> 作为默认的 metavar 值（而非参数的 <code>dest</code>）</li>
<li><code>RawDescriptionCmd2HelpFormatter</code> - 保留 description 和 epilog 中的格式</li>
<li><code>RawTextCmd2HelpFormatter</code> - 保留所有帮助文本的格式</li>
</ul><p>默认的 <code>Cmd2HelpFormatter</code> 类继承自 <code>argparse.HelpFormatter</code>。如果你需要不同的行为，请将所需的类传递给 argparse 解析器的 <code>formatter_class</code> 参数，例如 <code>formatter_class=ArgumentDefaultsCmd2HelpFormatter</code>。</p></section></section><section><h2>参数列表<a href="#参数列表"><span>#</span></a></h2><p><code>cmd2</code> 的默认行为是将用户输入直接作为字符串传递给你的 <code>do_*</code> 方法。传递给方法的对象实际上是一个 <code>cmd2.Statement</code> 对象，它具有一些可能有用的额外属性，包括 <code>arg_list</code> 和 <code>argv</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>CmdLineApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""Example cmd2 application."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>do_say</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>statement</span></span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span># statement contains a string</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(statement)</span></span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>do_speak</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>statement</span></span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span># statement also has a list of arguments</span></div></div><div><div><div>10</div></div><div><span>        </span><span># quoted arguments remain quoted</span></div></div><div><div><div>11</div></div><div><span>        </span><span>for</span><span> arg </span><span>in</span><span> statement.arg_list:</span></div></div><div><div><div>12</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(arg)</span></span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>do_articulate</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>statement</span></span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span># statement.argv contains the command</span></div></div><div><div><div>16</div></div><div><span>        </span><span># and the arguments, which have had quotes</span></div></div><div><div><div>17</div></div><div><span>        </span><span># stripped</span></div></div><div><div><div>18</div></div><div><span>        </span><span>for</span><span> arg </span><span>in</span><span> statement.argv:</span></div></div><div><div><div>19</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(arg)</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如果你不想访问传递给 <code>do_*</code> 方法的字符串上的额外属性，你仍然可以让 <code>cmd2</code> 对用户输入应用 shell 解析规则，并传递参数列表而非字符串。只需在那些应接收参数列表而非字符串的方法上应用 <code>@with_argument_list</code> 装饰器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> with_argument_list</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>CmdLineApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""Example cmd2 application."""</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>do_say</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>cmdline</span></span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span># cmdline contains a string</span></div></div><div><div><div>9</div></div><div><span>        </span><span>pass</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>@with_argument_list</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_speak</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arglist</span></span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span># arglist contains a list of arguments</span></div></div><div><div><div>14</div></div><div><span>        </span><span>pass</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>未知位置参数<a href="#未知位置参数"><span>#</span></a></h2><p>要将所有未知参数作为字符串列表传递给你的命令，请使用 <code>@with_argparser(..., with_unknown_args=True)</code> 装饰器来装饰命令方法。</p><p>以下是具体示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd2ArgumentParser, with_argparser</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>dir_parser </span><span>=</span><span> </span><span>Cmd2ArgumentParser</span><span>()</span></span></div></div><div><div><div>4</div></div><div><span><span>dir_parser.</span><span>add_argument</span><span>(</span></span><span>"-l"</span><span>, </span><span>"--long"</span><span>, </span><span>action</span><span>=</span><span>"store_true"</span><span>, </span><span>help</span><span>=</span><span>"display in long format with one item per line"</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>@with_argparser</span><span><span>(</span><span>dir_parser</span><span>,</span><span> </span></span><span>with_unknown_args</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>do_dir</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>,</span><span><span> </span><span>unknown</span></span><span>):</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"""List contents of current directory."""</span></div></div><div><div><div>10</div></div><div><span>    </span><span># No arguments for this command</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> unknown:</span></div></div><div><div><div>12</div></div><div><span>        </span><span>self</span><span><span>.</span><span>perror</span><span>(</span></span><span>"dir does not take any positional arguments:"</span><span>)</span></div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.</span><span>do_help</span><span>(</span></span><span>"dir"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>.last_result </span><span>=</span><span> </span></span><span>"Bad arguments"</span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>    </span><span># Get the contents as a list</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>contents </span><span>=</span><span> os.</span><span>listdir</span><span>(</span><span>self</span><span>.cwd)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>使用自定义命名空间<a href="#使用自定义命名空间"><span>#</span></a></h2><p>在某些情况下，可能需要编写依赖于应用程序状态数据的自定义 <code>argparse</code> 代码。为了在仍然允许使用装饰器的同时支持这种能力，<code>@with_argparser</code> 有一个名为 <code>ns_provider</code> 的可选参数。</p><p><code>ns_provider</code> 是一个接受 <code>cmd2.Cmd</code> 对象作为参数并返回 <code>argparse.Namespace</code> 的可调用对象：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Callable[[cmd2.Cmd], argparse.Namespace]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>settings_ns_provider</span><span>(</span><span>self</span><span>) -&gt; argparse.Namespace:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""Populate an argparse Namespace with current settings"""</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>ns </span><span>=</span><span> argparse.</span><span>Namespace</span><span>()</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>ns.app_settings </span><span>=</span><span> </span><span>self</span><span>.settings</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> ns</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要将此函数与 <code>@with_argparser</code> 装饰器一起使用，按如下方式操作：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_argparser</span><span><span>(</span><span>my_parser</span><span>,</span><span> </span></span><span>ns_provider</span><span><span>=</span><span>settings_ns_provider</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>命名空间由装饰器传递给 <code>argparse</code> 的解析函数，使你的自定义代码能够访问其解析逻辑所需的状态数据。</p></section><section><h2>子命令<a href="#子命令"><span>#</span></a></h2><p>使用 <code>@with_argparser</code> 装饰器的命令支持子命令。语法基于 argparse 的 sub-parsers。</p><p>你可以为命令添加多层子命令。<code>cmd2</code> 会自动遍历和补全所有使用 argparse 的命令的子命令。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py" target="_blank">argparse_example</a> 示例，了解更多关于如何在 <code>cmd2</code> 应用中使用子命令的信息。</p><p><code>@as_subcommand_to</code> 装饰器使得添加子命令变得简单。</p></section><section><h2>Argparse 扩展<a href="#argparse-扩展"><span>#</span></a></h2><p><code>cmd2</code> 用范围元组功能增强了标准的 <code>argparse.nargs</code>：</p><ul>
<li><code>nargs=(5,)</code> - 接受 5 个或更多项</li>
<li><code>nargs=(8, 12)</code> - 接受 8 到 12 项</li>
</ul><p><code>cmd2</code> 还提供了 <code>Cmd2ArgumentParser</code> 类，它继承自 <code>argparse.ArgumentParser</code> 并改进了错误和帮助输出。</p></section><section><h2>装饰器顺序<a href="#装饰器顺序"><span>#</span></a></h2><p>如果你在 <code>@cmd2.with_argparser</code> 之外还使用了自定义装饰器，那么自定义装饰器相对于 <code>cmd2</code> 装饰器的顺序会影响运行时行为和 <code>argparse</code> 错误处理。这不是 <code>cmd2</code> 特有的，这只是 Python 中装饰器工作方式的一个副作用。要了解更多关于装饰器如何工作的信息，请参阅 <a href="https://realpython.com/primer-on-python-decorators" target="_blank">decorator_primer</a>。</p><p>如果你希望自定义装饰器的运行时行为在 <code>argparse</code> 错误时也能执行，则该装饰器需要放在 <code>argparse</code> 装饰器<strong>之后</strong>，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>@cmd2</span><span>.</span><span>with_argparser</span></span><span><span>(</span><span>foo_parser</span><span>)</span></span></div></div><div><div><div>2</div></div><div><span>@my_decorator</span></div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>do_foo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> argparse.Namespace</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""foo docs"""</span></div></div><div><div><div>5</div></div><div><span>    </span><span>pass</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但如果你<strong>不希望</strong>自定义装饰器的运行时行为在 <code>argparse</code> 错误时执行，则该装饰器需要放在 <code>argparse</code> 装饰器<strong>之前</strong>，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@my_decorator</span></div></div><div><div><div>2</div></div><div><span><span>@cmd2</span><span>.</span><span>with_argparser</span></span><span><span>(</span><span>bar_parser</span><span>)</span></span></div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>do_bar</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> argparse.Namespace</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""bar docs"""</span></div></div><div><div><div>5</div></div><div><span>    </span><span>pass</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/help_categories.py" target="_blank">help_categories</a> 示例以具体的方式演示了以上两种情况。</p></section><section><h2>保留的参数名称<a href="#保留的参数名称"><span>#</span></a></h2><p><code>cmd2</code> 的 <code>@with_argparser</code> 装饰器会向 argparse Namespace 添加以下属性。为避免命名冲突，请不要将这些名称用于你的 argparse 参数。</p><ul>
<li><code>cmd2_statement</code> - 解析命令行时创建的 <code>cmd2.Statement</code> 对象</li>
<li><code>cmd2_subcommand_func</code> - 子命令处理函数，如果未设置则为 <code>None</code></li>
</ul></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/async_builtin_clipboard/</id>
      <title type="text">Python cmd2：异步命令、内置命令与剪贴板集成</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/async_builtin_clipboard/"/>
      <summary type="text">介绍 cmd2 的异步命令实现、内置命令列表及其用法，以及剪贴板集成功能。</summary>
      <content type="html"><![CDATA[<section><h1>异步命令<a href="#异步命令"><span>#</span></a></h1><p><code>cmd2</code> 构建在 Python 标准库的 <code>cmd</code> 模块之上，该模块本质上是同步的。这意味着 <code>do_*</code> 命令方法必须是同步函数。</p><p>不过，你仍然可以通过在后台线程中运行 <code>asyncio</code> 事件循环并桥接调用的方式，将异步代码（使用 <code>asyncio</code> 和 <code>async</code>/<code>await</code>）集成到你的 <code>cmd2</code> 应用中。</p><section><h2><code>with_async_loop</code> 装饰器<a href="#with_async_loop-装饰器"><span>#</span></a></h2><p>处理此问题的一种简洁方式是定义一个装饰器来包装你的 <code>async def</code> 命令。该装饰器负责：</p><ol>
<li>启动一个带有 <code>asyncio</code> 事件循环的后台线程（如果尚未运行）</li>
<li>将命令的协程提交到该事件循环</li>
<li>同步等待结果，以便 <code>cmd2</code> 接口按预期工作（阻塞直到命令完成）</li>
</ol><section><h3>实现示例<a href="#实现示例"><span>#</span></a></h3><p>以下是实现此类装饰器并在应用中使用的示例。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> asyncio</span></div></div><div><div><div>2</div></div><div><span>import</span><span> functools</span></div></div><div><div><div>3</div></div><div><span>import</span><span> threading</span></div></div><div><div><div>4</div></div><div><span>from</span><span> typing </span><span>import</span><span> Any, Callable</span></div></div><div><div><div>5</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># Global event loop and lock</span></div></div><div><div><div>8</div></div><div><span><span>_event_loop </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>9</div></div><div><span><span>_event_lock </span><span>=</span><span> threading.</span><span>Lock</span><span>()</span></span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>def</span><span> </span><span>_get_event_loop</span><span>() -&gt; asyncio.AbstractEventLoop:</span></div></div><div><div><div>13</div></div><div><span>    </span><span>"""Get or create the background event loop."""</span></div></div><div><div><div>14</div></div><div><span>    </span><span>global</span><span> _event_loop</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> _event_loop </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>17</div></div><div><span>        </span><span>with</span><span> _event_lock:</span></div></div><div><div><div>18</div></div><div><span>            </span><span>if</span><span> _event_loop </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>19</div></div><div><span><span>                </span></span><span>_event_loop </span><span>=</span><span> asyncio.</span><span>new_event_loop</span><span>()</span></div></div><div><div><div>20</div></div><div><span><span>                </span></span><span>thread </span><span>=</span><span> threading.</span><span>Thread</span><span>(</span></div></div><div><div><div>21</div></div><div><span>                    </span><span>target</span><span><span>=</span><span>_event_loop.run_forever,</span></span></div></div><div><div><div>22</div></div><div><span>                    </span><span>name</span><span>=</span><span>"Async Runner"</span><span>,</span></div></div><div><div><div>23</div></div><div><span>                    </span><span>daemon</span><span>=</span><span>True</span><span>,</span></div></div><div><div><div>24</div></div><div><span><span>                </span></span><span>)</span></div></div><div><div><div>25</div></div><div><span><span>                </span></span><span>thread.</span><span>start</span><span>()</span></div></div><div><div><div>26</div></div><div><span>    </span><span>return</span><span> _event_loop</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>def</span><span> </span><span>with_async_loop</span><span>(</span><span>func</span><span>:</span><span> Callable[</span><span>...</span><span>, Any]</span><span><span>) -&gt; Callable[</span><span>...</span><span>, Any]:</span></span></div></div><div><div><div>30</div></div><div><span>    </span><span>"""Decorator to run a command method asynchronously in a background thread."""</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span><span>    </span></span><span><span>@functools</span><span>.</span><span>wraps</span></span><span><span>(</span><span>func</span><span>)</span></span></div></div><div><div><div>33</div></div><div><span>    </span><span>def</span><span> </span><span>wrapper</span><span>(</span><span>self</span><span>:</span><span> cmd2.Cmd</span><span>,</span><span> </span><span>*</span><span>args</span><span>:</span><span> Any</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>:</span><span> Any</span><span>) -&gt; Any:</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>loop </span><span>=</span><span> </span><span>_get_event_loop</span><span>()</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>coro </span><span>=</span><span> </span><span>func</span><span>(</span><span>self</span><span>, *args, **kwargs)</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>future </span><span>=</span><span> asyncio.</span><span>run_coroutine_threadsafe</span><span>(coro, loop)</span></div></div><div><div><div>37</div></div><div><span>        </span><span>return</span><span><span> future.</span><span>result</span><span>()</span></span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>    </span><span>return</span><span> wrapper</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>class</span><span><span> </span><span>AsyncApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>@with_async_loop</span></div></div><div><div><div>44</div></div><div><span>    </span><span>async</span><span> </span><span>def</span><span> </span><span>do_my_async</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>45</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Starting async work..."</span><span>)</span></div></div><div><div><div>46</div></div><div><span>        </span><span>await</span><span><span> asyncio.</span><span>sleep</span><span>(</span></span><span>1.0</span><span>)</span></div></div><div><div><div>47</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Async work complete!"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>另请参阅<a href="#另请参阅"><span>#</span></a></h2><ul>
<li><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/async_commands.py" target="_blank">async_commands.py</a> - 完整示例代码</li>
<li><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/async_call.py" target="_blank">async_call.py</a> - 展示如何在不使用装饰器的情况下进行单个异步调用的替代示例</li>
</ul></section></section>
<section><h1>内置命令<a href="#内置命令"><span>#</span></a></h1><p>继承 <code>cmd2.Cmd</code> 的应用程序会自动获得一系列可能对用户有用的命令。如果开发者不希望某些命令成为应用的一部分，可以<a href="#%E7%A7%BB%E9%99%A4%E5%86%85%E7%BD%AE%E5%91%BD%E4%BB%A4">移除内置命令</a>。</p><section><h2>内置命令列表<a href="#内置命令列表"><span>#</span></a></h2><section><h3>alias<a href="#alias"><span>#</span></a></h3><p>此命令通过子命令 <code>create</code>、<code>delete</code> 和 <code>list</code> 来管理别名。详见<a href="../shortcuts_aliases_macros/#%E5%88%AB%E5%90%8D">别名</a>。</p></section><section><h3>edit<a href="#edit"><span>#</span></a></h3><p>此命令启动编辑器程序并指示其打开指定的文件名。示例：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>Cmd</span><span>) </span><span>edit</span><span> </span><span>~/.ssh/config</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>启动的程序由 <a href="../settings_plugins/#editor">editor</a> 设置的值决定。</p></section><section><h3>help<a href="#help"><span>#</span></a></h3><p>此命令列出可用命令或提供特定命令的详细帮助。使用 <code>-v/--verbose</code> 参数调用时，会显示每个命令的简要描述。详见<a href="../embedded_output_help/#%E5%B8%AE%E5%8A%A9">帮助</a>。</p></section><section><h3>history<a href="#history"><span>#</span></a></h3><p>此命令允许你查看、运行、编辑、保存或清除之前输入的命令历史。详见<a href="../history/#%E5%91%BD%E4%BB%A4%E5%8E%86%E5%8F%B2">命令历史</a>。</p></section><section><h3>ipy（可选）<a href="#ipy可选"><span>#</span></a></h3><p>此可选命令会进入一个交互式 IPython shell。详见 <a href="../embedded_output_help/#ipython%E5%8F%AF%E9%80%89">IPython（可选）</a>。</p></section><section><h3>macro<a href="#macro"><span>#</span></a></h3><p>此命令通过子命令 <code>create</code>、<code>delete</code> 和 <code>list</code> 来管理宏。宏类似于别名，但可以包含参数占位符。详见<a href="../shortcuts_aliases_macros/#%E5%AE%8F">宏</a>。</p></section><section><h3>py（可选）<a href="#py可选"><span>#</span></a></h3><p>此可选命令调用 Python 命令或 shell。详见<a href="../embedded_output_help/#%E5%B5%8C%E5%85%A5%E5%BC%8F-python-shell">嵌入式 Python Shell</a>。</p></section><section><h3>quit<a href="#quit"><span>#</span></a></h3><p>此命令退出 <code>cmd2</code> 应用。</p></section><section><h3>run_pyscript<a href="#run_pyscript"><span>#</span></a></h3><p>此命令在 <code>cmd2</code> 应用内部运行 Python 脚本文件。详见 <a href="../embedded_output_help/#python%E8%84%9A%E6%9C%AC">Python 脚本</a>。</p></section><section><h3>run_script<a href="#run_script"><span>#</span></a></h3><p>此命令运行以 ASCII 或 UTF-8 文本编码的脚本文件中的命令。详见<a href="../scripting/#%E5%91%BD%E4%BB%A4%E8%84%9A%E6%9C%AC">命令脚本</a>。</p></section><section><h3>_relative_run_script<a href="#_relative_run_script"><span>#</span></a></h3><p><strong>此命令对最终用户是隐藏的。</strong> 它类似于 <a href="#run_script">run_script</a>，但使用相对于当前正在执行的脚本的路径。这在你有脚本运行其他脚本的情况下非常有用。详见<a href="../scripting/#%E8%BF%90%E8%A1%8C%E5%91%BD%E4%BB%A4%E8%84%9A%E6%9C%AC">运行命令脚本</a>。</p></section><section><h3>set<a href="#set"><span>#</span></a></h3><p>所有用户可设置的参数及其简要注释可以在运行中的应用内查看：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) set</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>Name                            Value      Description</span></div></div><div><div><div>3</div></div><div><span><span> </span></span><span>allow_style                     Terminal   Allow ANSI text style sequences in output (valid values: Always, Never, Terminal)</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>debug                           False      Show full traceback on exception</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>echo                            False      Echo command issued into output</span></div></div><div><div><div>6</div></div><div><span><span> </span></span><span>editor                          vim        Program used by 'edit'</span></div></div><div><div><div>7</div></div><div><span><span> </span></span><span>max_column_completion_results   7          Maximum number of completion results to display in a single column</span></div></div><div><div><div>8</div></div><div><span><span> </span></span><span>max_completion_table_items      50         Maximum number of completion results allowed for a completion table to appear</span></div></div><div><div><div>9</div></div><div><span><span> </span></span><span>quiet                           False      Don't print nonessential feedback</span></div></div><div><div><div>10</div></div><div><span><span> </span></span><span>scripts_add_to_history          True       Scripts and pyscripts add commands to history</span></div></div><div><div><div>11</div></div><div><span><span> </span></span><span>timing                          False      Report execution times</span></div></div><div><div><div>12</div></div><div><span><span> </span></span><span>traceback_show_locals           False      Display local variables in tracebacks</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在运行应用时，可以使用 <code>set</code> 命令来设置这些用户可设置参数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) set allow_style Never</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>详见<a href="../settings_plugins/#%E8%AE%BE%E7%BD%AE">设置</a>。</p></section><section><h3>shell<a href="#shell"><span>#</span></a></h3><p>在操作系统 shell 提示符下执行命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) shell pwd -P</span></div></div><div><div><div>2</div></div><div><span>/usr/local/bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>shortcuts<a href="#shortcuts"><span>#</span></a></h3><p>此命令列出可用的快捷方式。详见<a href="../shortcuts_aliases_macros/#%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F">快捷方式</a>。</p></section></section><section><h2>移除内置命令<a href="#移除内置命令"><span>#</span></a></h2><p>开发者可能不希望向应用用户提供 <code>cmd2.Cmd</code> 中的所有内置命令。要移除某个命令，你需要在运行时从 <code>cmd2.Cmd</code> 对象中删除实现该命令的方法。例如，如果你想从应用中移除 <a href="#shell">shell</a> 命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>NoShellApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""A simple cmd2 application."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>delattr</span><span>(cmd2.Cmd, </span><span>"do_shell"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>剪贴板集成<a href="#剪贴板集成"><span>#</span></a></h1><p>几乎每个操作系统都有某种短期存储区域的概念，可以被任何程序访问。通常这被称为剪贴板，但有时人们也称其为粘贴缓冲区。</p><p><code>cmd2</code> 使用 <a href="https://github.com/asweigart/pyperclip" target="_blank">pyperclip</a> 模块与操作系统剪贴板集成。命令输出可以通过在命令末尾添加大于号来发送到剪贴板：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mycommand args &gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以将其理解为将输出重定向到一个临时的、匿名的位置：剪贴板。你也可以通过在命令末尾添加两个大于号来将输出追加到剪贴板的当前内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mycommand arg1 arg2 &gt;&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>命令输出重定向功能仅直接在 <code>cmd2</code> 命令中有效。在将输出通过管道传递给 shell 命令后，该功能将不起作用。</p></div></div><section><h2>开发者指南<a href="#开发者指南"><span>#</span></a></h2><p>你可以通过设置 <code>cmd2.Cmd.allow_clipboard</code> 属性来控制是否允许用户使用上述将输出添加到操作系统剪贴板的功能。默认值为 <code>True</code>。将其设为 <code>False</code> 后，上述功能将生成错误消息而不是将输出添加到剪贴板。<code>cmd2.Cmd.allow_clipboard</code> 可以在初始化时设置，也可以在代码中随时更改。</p><p>如果你希望基于 <code>cmd2</code> 的应用能够以其他或替代方式使用剪贴板，可以使用 <code>cmd2.clipboard</code> 模块中提供的方法（在 Windows、macOS 和 Linux 上的工作方式一致）。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/command_completion_disable/</id>
      <title type="text">Python cmd2：命令、补全与禁用命令</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/command_completion_disable/"/>
      <summary type="text">详细介绍 cmd2 的命令创建、Tab 补全机制以及命令的禁用、隐藏和移除功能。</summary>
      <content type="html"><![CDATA[<section><h1>命令<a href="#命令"><span>#</span></a></h1><p><code>cmd2</code> 的设计目标是让你能够轻松创建新命令。这些命令构成了应用程序的核心。如果你最初是使用 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 来编写应用，那么当你迁移到 <code>cmd2</code> 时，你已构建的所有命令都将正常工作。然而，<code>cmd2</code> 提供了更多功能，你可以利用它们为命令添加更强大的特性，使命令更容易编写。在介绍所有精彩功能之前，让我们先简要讨论如何在应用中创建新命令。</p><section><h2>基本命令<a href="#基本命令"><span>#</span></a></h2><p>最简单的 <code>cmd2</code> 应用如下所示：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python</span></div></div><div><div><div>2</div></div><div><span>"""A simple cmd2 application."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>"""A simple cmd2 application."""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>c </span><span>=</span><span> </span><span>App</span><span>()</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(c.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这个应用继承了 <code>cmd2.Cmd</code>，但没有自己的代码，因此所有功能（而且相当丰富）都是继承来的。让我们在这个应用中创建一个名为 <code>echo</code> 的简单命令，它输出传入的所有参数。将以下方法添加到类中：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(line)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当你在 <code>cmd2</code> 提示符下输入内容时，第一个以空格分隔的单词会被视为命令名。<code>cmd2</code> 会查找名为 <code>do_命令名</code> 的方法。如果存在，就调用该方法，并将用户输入的其余部分作为第一个参数传递。如果不存在，<code>cmd2</code> 会打印错误消息。由于这种行为，创建新命令只需在类中定义一个具有适当名称的新方法即可。这与使用 Python 标准库中的 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 模块创建命令的方式完全一致。</p><div><div><div></div><div>Note</div></div><div><p>如果你不熟悉 <code>poutput()</code> 方法，请参阅<a href="../embedded_output_help/#%E7%94%9F%E6%88%90%E8%BE%93%E5%87%BA">生成输出</a>。</p></div></div></section><section><h2>语句对象<a href="#语句对象"><span>#</span></a></h2><p>一个命令接收一个参数：包含用户输入其余部分的字符串。然而，在 <code>cmd2</code> 中，这个字符串实际上是一个 <code>cmd2.Statement</code> 对象，它是 <code>str</code> 的子类，以保持与 <code>cmd</code> 的向后兼容性。</p><p><code>cmd2</code> 比 <a href="https://docs.python.org/3/library/cmd.html" target="_blank">cmd</a> 模块拥有更复杂的解析引擎。此解析处理包括：</p><ul>
<li>带引号的参数</li>
<li>输出重定向和管道</li>
<li>多行命令</li>
<li>快捷方式、别名和宏展开</li>
</ul><p>除了从用户输入中解析所有这些元素外，<code>cmd2</code> 还有代码来使这些功能正常工作——对你和你在应用中编写的命令来说几乎是透明的。通过将 <code>Statement</code> 对象（而非普通字符串）传递给你的命令，你可以了解 <code>cmd2</code> 在命令接收到用户输入之前对其做了什么处理。你还可以避免编写大量解析代码，因为 <code>cmd2</code> 让你可以访问它已经解析好的内容。</p><p><code>Statement</code> 对象是 <code>str</code> 的子类，包含以下属性：</p><p><strong>command</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>调用的命令名称。你已经通过 `cmd2` 调用的方法知道了这一点，</span></div></div><div><div><div>2</div></div><div><span>但有时将其作为字符串会很方便，例如当你想在错误消息中包含命令名称时。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>args</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>包含命令参数的字符串，已移除输出重定向或到 shell 命令的管道部分。</span></div></div><div><div><div>2</div></div><div><span>实际上，`Statement` 对象的"字符串"值也已移除了所有输出重定向和管道子句。引号保留在字符串中。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>command_and_args</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>仅包含命令和参数的字符串，已移除输出重定向或到 shell 命令的管道部分。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>argv</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>类似 `sys.argv` 的参数列表，包括作为 `argv[0]` 的命令和作为列表后续项的参数。</span></div></div><div><div><div>2</div></div><div><span>参数周围的引号将被去除，命令中的输出重定向或管道部分也会被去除。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>raw</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用户输入的完整内容，原样保留。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>terminator</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>用于结束多行命令的字符。</span></div></div><div><div><div>2</div></div><div><span>你可以配置多个终止字符，此属性会告诉你用户输入的是哪一个。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于许多简单命令（如上面的 <code>echo</code> 命令），你可以忽略 <code>Statement</code> 对象及其所有属性，直接将传递的值作为字符串使用。你也可以选择使用 <code>argv</code> 属性来进行更复杂的参数处理。在你深入之前，应该先查看 <code>cmd2</code> 提供的<a href="../argument_processing/">参数处理</a>功能。</p></section><section><h2>返回值<a href="#返回值"><span>#</span></a></h2><p>大多数命令不应返回任何内容（通过省略 <code>return</code> 语句或 <code>return None</code>）。这表示你的命令已完成（无论是否有错误），<code>cmd2</code> 应提示用户输入更多内容。</p><p>如果你从命令方法返回 <code>True</code> 或任何<a href="https://www.freecodecamp.org/news/truthy-and-falsy-values-in-python/" target="_blank">真值</a>，这表示 <code>cmd2</code> 应停止提示用户输入并正常退出。<code>cmd2</code> 已经包含一个 <code>quit</code> 命令，但如果你想创建另一个名为 <code>finish</code> 的命令，可以这样做：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_finish</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""Exit the application"""</span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span> </span><span>True</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>退出码<a href="#退出码"><span>#</span></a></h2><p><code>cmd2</code> 具有支持 POSIX shell 退出码的基本基础设施。<code>cmd2.Cmd</code> 对象在实例化时将 <code>exit_code</code> 属性设置为零。此属性的值由 <code>cmdloop()</code> 调用返回。因此，如果你在代码中不对此属性做任何操作，<code>cmdloop()</code> 将（几乎）总是返回零。有一些内置的 <code>cmd2</code> 命令会在发生错误时将 <code>exit_code</code> 设置为 <code>1</code>。</p><p>你可以使用此功能轻松地向操作系统 shell 返回自定义值：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python</span></div></div><div><div><div>2</div></div><div><span>"""A simple cmd2 application."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>"""A simple cmd2 application."""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>do_bail</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>12</div></div><div><span>    </span><span>"""Exit the application"""</span></div></div><div><div><div>13</div></div><div><span>    </span><span>self</span><span><span>.</span><span>perror</span><span>(</span></span><span>"fatal error, exiting"</span><span>)</span></div></div><div><div><div>14</div></div><div><span>    </span><span>self</span><span><span>.exit_code </span><span>=</span><span> </span></span><span>2</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span> </span><span>True</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>19</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>c </span><span>=</span><span> </span><span>App</span><span>()</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(c.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如果应用从 <code>bash</code> 操作系统 shell 运行，你将看到以下交互：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>Cmd</span><span>) </span><span>bail</span></div></div><div><div><div>2</div></div><div><span>fatal</span><span> </span><span>error,</span><span> </span><span>exiting</span></div></div><div><div><div>3</div></div><div><span>$</span><span> </span><span>echo</span><span> </span><span>$?</span></div></div><div><div><div>4</div></div><div><span>2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在命令或钩子函数中引发 <code>SystemExit(code)</code> 或调用 <code>sys.exit(code)</code> 也会设置 <code>self.exit_code</code> 并停止程序。</p></section><section><h2>异常处理<a href="#异常处理"><span>#</span></a></h2><p>你可以选择捕获和处理命令方法中发生的任何异常。如果命令方法引发异常，<code>cmd2</code> 会捕获它并为你显示。<a href="../settings_plugins/#debug">debug 设置</a>控制异常的显示方式。如果 <code>debug</code> 为 <code>False</code>（默认值），<code>cmd2</code> 将显示异常名称和消息。如果 <code>debug</code> 为 <code>True</code>，<code>cmd2</code> 将显示回溯信息，然后显示异常名称和消息。</p><p>有一些命令可以引发的异常不会按上述方式打印：</p><ul>
<li><code>cmd2.exceptions.SkipPostcommandHooks</code> - 跳过所有后命令钩子，不打印异常</li>
<li><code>cmd2.exceptions.Cmd2ArgparseError</code> - 行为类似于 <code>SkipPostcommandHooks</code></li>
<li><code>SystemExit</code> - <code>stop</code> 将被设为 <code>True</code>，尝试停止命令循环</li>
<li><code>KeyboardInterrupt</code> - 如果在文本脚本中运行且 <code>stop</code> 尚未为 True 时引发，用于停止脚本</li>
</ul><p>所有其他 <code>BaseException</code> 不会被 <code>cmd2</code> 捕获，将直接引发。</p></section><section><h2>禁用或隐藏命令<a href="#禁用或隐藏命令"><span>#</span></a></h2><p>详见<a href="#%E7%A6%81%E7%94%A8%E5%91%BD%E4%BB%A4">禁用命令</a>了解如何：</p><ul>
<li>移除 <code>cmd2</code> 中包含的命令</li>
<li>从帮助菜单中隐藏命令</li>
<li>在运行时动态禁用和重新启用命令</li>
</ul></section><section><h2>模块化命令与加载/卸载命令<a href="#模块化命令与加载卸载命令"><span>#</span></a></h2><p>详见<a href="../modular_commands/">模块化命令</a>了解如何：</p><ul>
<li>在单独的 <code>CommandSet</code> 模块中定义命令</li>
<li>在运行时动态加载或卸载命令</li>
</ul></section></section>
<section><h1>补全<a href="#补全"><span>#</span></a></h1><p><code>cmd2.Cmd</code> 为所有适用的内置命令添加了文件系统路径的 Tab 补全，包括：</p><ul>
<li><a href="../async_builtin_clipboard/#edit">edit</a></li>
<li><a href="../async_builtin_clipboard/#run_pyscript">run_pyscript</a></li>
<li><a href="../async_builtin_clipboard/#run_script">run_script</a></li>
<li><a href="../async_builtin_clipboard/#shell">shell</a></li>
</ul><p><code>cmd2.Cmd</code> 还为 <a href="../async_builtin_clipboard/#shell">shell</a> 命令添加了 shell 命令的 Tab 补全。</p><p>为自定义命令添加相同的文件系统路径补全非常简单。假设你通过实现 <code>do_foo</code> 方法定义了一个自定义命令 <code>foo</code>。要为 <code>foo</code> 命令启用路径补全，请在继承自 <code>cmd2.Cmd</code> 的类中添加类似以下的代码行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>complete_foo </span><span>=</span><span> cmd2.Cmd.path_complete</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这将在你的类中定义 <code>complete_foo</code> prompt-toolkit 补全方法，并使其使用与内置命令相同的路径补全逻辑。</p><p>内置逻辑还提供了一些更高级的路径补全功能，例如你只想匹配目录的情况。假设你有一个通过 <code>do_bar</code> 方法实现的自定义命令 <code>bar</code>。你可以通过添加类似以下的代码行来仅为此命令启用目录路径补全：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># Make sure you have an "import functools" somewhere at the top</span></div></div><div><div><div>2</div></div><div><span><span>complete_bar </span><span>=</span><span> functools.</span><span>partialmethod</span><span>(cmd2.Cmd.path_complete, </span></span><span>path_filter</span><span><span>=</span><span>os.path.isdir)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h2>内置 Tab 补全函数<a href="#内置-tab-补全函数"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 提供以下 Tab 补全函数：</p><ul>
<li>
<p><code>basic_complete</code> - 针对列表进行 Tab 补全的辅助方法</p>
</li>
<li>
<p><code>path_complete</code> - 提供灵活的文件系统路径 Tab 补全的辅助方法</p>
<blockquote><ul>
<li>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/paged_output.py" target="_blank">paged_output</a> 示例了解简单用例</li>
<li>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/python_scripting.py" target="_blank">python_scripting</a> 示例了解更完整的用例</li>
</ul></blockquote>
</li>
<li>
<p><code>delimiter_complete</code> - 针对列表进行 Tab 补全的辅助方法，但每个匹配项按分隔符拆分</p>
<blockquote><ul>
<li>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/basic_completion.py" target="_blank">basic_completion</a> 示例了解如何使用此功能</li>
</ul></blockquote>
</li>
</ul></section><section><h2>补全过程中引发异常<a href="#补全过程中引发异常"><span>#</span></a></h2><p>有时在 Tab 补全过程中会发生错误，需要向用户报告消息。包括以下示例情况：</p><ul>
<li>读取数据库以获取补全数据集失败</li>
<li>确定正在补全的数据集的前一个命令行参数无效</li>
<li>Tab 补全提示</li>
</ul><p><code>cmd2</code> 为此提供了 <code>CompletionError</code> 异常类。如果发生错误，显示消息比显示堆栈跟踪更合适，就引发 <code>CompletionError</code>。默认情况下，消息以红色（错误样式）显示。然而，<code>CompletionError</code> 有一个名为 <code>apply_style</code> 的成员。如果不想应用错误样式，将其设为 False。例如，<code>ArgparseCompleter</code> 在显示补全提示时将其设为 False。</p></section><section><h2>使用 argparse 装饰器进行 Tab 补全<a href="#使用-argparse-装饰器进行-tab-补全"><span>#</span></a></h2><p>当使用 <code>cmd2</code> 的 <code>@with_argparser</code> 装饰器时，<code>cmd2</code> 提供标志名称的自动 Tab 补全。</p><p>参数值的 Tab 补全可以通过 <code>Cmd2ArgumentParser.add_argument()</code> 的三个参数之一来配置：</p><ul>
<li><code>choices</code></li>
<li><code>choices_provider</code></li>
<li><code>completer</code></li>
</ul><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py" target="_blank">argparse_example</a> 示例了解如何使用 <code>choices</code> 参数。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_completion.py" target="_blank">argparse_completion</a> 示例了解如何使用 <code>choices_provider</code> 参数。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py" target="_blank">argparse_example</a> 或 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_completion.py" target="_blank">argparse_completion</a> 示例了解如何使用 <code>completer</code> 参数。</p><p>当为使用 <code>@with_argparser</code> 装饰器的 <code>cmd2</code> 命令进行标志或参数值的 Tab 补全时，<code>cmd2</code> 会跟踪状态，一旦某个标志已经被提供，就不会再次尝试补全它。当没有补全结果时，会显示当前参数的提示以帮助用户。</p></section><section><h2>CompletionItem 提供额外上下文<a href="#completionitem-提供额外上下文"><span>#</span></a></h2><p>在补全数据库中的唯一 ID 等内容时，为用户正在补全的项目提供一些额外上下文（如描述）通常很有帮助。为此，<code>cmd2</code> 定义了 <code>CompletionItem</code> 类，它可以作为三个补全参数（<code>choices</code>、<code>choices_provider</code> 和 <code>completer</code>）中任何一个的返回值。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_completion.py" target="_blank">argparse_completion</a> 示例或内置 <a href="../async_builtin_clipboard/#set">set</a> 命令的实现了解如何使用。</p></section><section><h2>使用 <code>read_input()</code> 进行自定义补全<a href="#使用-read_input-进行自定义补全"><span>#</span></a></h2><p><code>cmd2</code> 提供了 <code>cmd2.Cmd.read_input</code> 作为 Python <code>input()</code> 函数的替代方案。<code>read_input</code> 支持可配置的 Tab 补全和上箭头历史记录。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/read_input.py" target="_blank">read_input</a> 示例了解用法。</p></section><section><h2>自定义补全菜单颜色<a href="#自定义补全菜单颜色"><span>#</span></a></h2><p><code>cmd2</code> 提供了自定义补全菜单项及其关联帮助文本的前景色和背景色的功能。详见主题文档中的<a href="../misc_packaging/#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A1%A5%E5%85%A8%E8%8F%9C%E5%8D%95%E9%A2%9C%E8%89%B2">自定义补全菜单颜色</a>。</p></section><section><h2>更多信息<a href="#更多信息"><span>#</span></a></h2><p>参阅 cmd2 的 argparse_utils API 了解关于 argparse 补全的更详细讨论。</p></section></section>
<section><h1>禁用命令<a href="#禁用命令"><span>#</span></a></h1><p><code>cmd2</code> 允许开发者：</p><ul>
<li>移除 <code>cmd2</code> 中包含的命令</li>
<li>阻止命令出现在帮助菜单中（隐藏命令）</li>
<li>在运行时禁用和重新启用命令</li>
</ul><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/remove_builtin_commands.py" target="_blank">remove_builtin_commands.py</a> 了解移除或隐藏内置命令的示例。</p><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/command_sets.py" target="_blank">command_sets.py</a> 了解在运行时动态启用和禁用自定义命令的示例。</p><section><h2>移除命令<a href="#移除命令"><span>#</span></a></h2><p>当命令被移除后，命令方法已从对象中删除。该命令不会显示在帮助中，也无法执行。如果你永远不希望某个内置命令成为应用的一部分，这种方法是合适的。在初始化代码中删除命令方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>    </span><span>class</span><span><span> </span><span>RemoveBuiltinCommand</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>        </span><span>"""An app which removes a built-in command from cmd2"""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>        </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>5</div></div><div><span>            </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>6</div></div><div><span>            </span><span># To remove built-in commands entirely, delete</span></div></div><div><div><div>7</div></div><div><span>            </span><span># the "do_*" function from the cmd2.Cmd class</span></div></div><div><div><div>8</div></div><div><span>            </span><span>del</span><span> cmd2.Cmd.do_edit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>隐藏命令<a href="#隐藏命令"><span>#</span></a></h2><p>当命令被隐藏后，它不会出现在帮助菜单中，也不会被 Tab 补全，但如果用户知道它的存在并输入该命令，它仍会被执行。你通过将命令添加到 <code>hidden_commands</code> 列表来隐藏它：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>HiddenCommands</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""An app which demonstrates how to hide a command"""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.hidden_commands.</span><span>append</span><span>(</span></span><span>"py"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如上所示，你通常会在初始化应用时执行此操作。如果你稍后决定取消隐藏某个命令，可以这样做：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>self</span><span><span>.hidden_commands </span><span>=</span><span> [cmd </span></span><span>for</span><span> cmd </span><span>in</span><span> </span><span>self</span><span>.hidden_commands </span><span>if</span><span><span> cmd </span><span>!=</span><span> </span></span><span>"py"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可能觉得列表推导式有些多余，更愿意这样做：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>self</span><span><span>.hidden_commands.</span><span>remove</span><span>(</span></span><span>"py"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你也许是对的，但如果 <code>py</code> 不在列表中，<code>remove()</code> 会引发 <code>ValueError</code>，而且如果列表中有多个相同的项，它只会移除第一个。</p></section><section><h2>禁用命令<a href="#禁用命令-1"><span>#</span></a></h2><p>禁用命令的一种方式是在命令方法中添加代码来判断是否应执行该命令。如果命令不应执行，你的代码可以打印适当的错误消息并返回。</p><p><code>cmd2</code> 还提供了另一种方式来实现同样的效果。以下是一个简单的应用，在门被锁时禁用 <code>open</code> 命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>DisabledCommands</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""An application which disables and enables commands"""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>do_lock</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span>self</span><span><span>.</span><span>disable_command</span><span>(</span></span><span>"open"</span><span>, </span><span>"you can't open the door because it is locked"</span><span>)</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"the door is locked"</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>do_unlock</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span>self</span><span><span>.</span><span>enable_command</span><span>(</span></span><span>"open"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"the door is unlocked"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_open</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>line</span></span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span>"""open the door"""</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"opening the door"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种方法还有一个额外的好处，就是将禁用的命令从帮助菜单中移除。但这种方法只适用于你提前知道命令应被禁用，且重新启用的条件也是提前已知的情况。</p></section><section><h2>禁用一类命令<a href="#禁用一类命令"><span>#</span></a></h2><p>你可以按<a href="../embedded_output_help/#%E5%91%BD%E4%BB%A4%E5%88%86%E7%B1%BB">命令分类</a>中所述对命令进行分组或分类。如果这样做，你可以通过一次方法调用来禁用和启用某个分类中的所有命令。假设你创建了一个名为”服务器信息”的命令分类，你可以禁用该分类中的所有命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>not_connected_msg </span><span>=</span><span> </span></span><span>"You must be connected to use this command"</span></div></div><div><div><div>2</div></div><div><span>self</span><span><span>.</span><span>disable_category</span><span>(</span></span><span>"Server Information"</span><span>, not_connected_msg)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>类似地，你可以重新启用某个分类中的所有命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>self</span><span><span>.</span><span>enable_category</span><span>(</span></span><span>"Server Information"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/help_categories.py" target="_blank">help_categories.py</a> 了解在运行时动态启用和禁用整个命令分类的示例。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/embedded_output_help/</id>
      <title type="text">Python cmd2：嵌入式 Python Shell、生成输出与帮助系统</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/embedded_output_help/"/>
      <summary type="text">详细介绍 cmd2 的嵌入式 Python/IPython Shell、输出生成方法（包括分页、着色、对齐）以及帮助系统和命令分类功能。</summary>
      <content type="html"><![CDATA[<section><h1>嵌入式 Python Shell<a href="#嵌入式-python-shell"><span>#</span></a></h1><section><h2>Python（可选）<a href="#python可选"><span>#</span></a></h2><p>如果 <code>cmd2.Cmd</code> 类在实例化时传入 <code>include_py=True</code>，则可选的 <code>py</code> 命令将可用，并启动一个交互式 Python shell：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>, </span><span>include_py</span><span>=</span><span>True</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Python shell 可以使用 <code>self.pyscript_name</code> 中命名的对象（默认为 <code>app</code>）来运行你应用中的 CLI 命令。这个封装提供了在你的 <code>cmd2</code> 应用中执行命令的能力，同时与完整的 <code>Cmd</code> 实例保持隔离。例如，可以使用 <code>app("command ...")</code> 来运行任何应用命令。</p><p>你可以选择通过将 <code>self.self_in_py</code> 设为 <code>True</code> 来启用对应用的完全访问。启用此标志会将 <code>self</code> 添加到 Python 会话中，它是对你的 <code>cmd2</code> 应用的引用。这对调试应用非常有用。</p><p>在 Python 会话中创建的任何局部或全局变量不会在 CLI 环境中持久存在。</p><p><code>self.py_locals</code> 中的所有内容在 Python 环境中始终可用。</p><p>所有这些参数也可通过 <code>run_pyscript</code> 命令运行的 Python 脚本使用：</p><ul>
<li>支持文件系统路径的 Tab 补全</li>
<li>能够向调用的脚本传递命令行参数</li>
</ul><p>此命令提供了比简单文本文件脚本更复杂和强大的脚本能力。Python 脚本可以包含条件控制流逻辑。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/python_scripting.py" target="_blank">python_scripting.py</a> 和 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/scripts/conditional.py" target="_blank">conditional.py</a> 脚本了解如何在自己的应用中实现这一点。参阅<a href="../scripting/">脚本</a>了解 <code>cmd2</code> 应用中两种脚本方法的说明。</p><p>以下是使用 <code>run_pyscript</code> 的简单示例以及 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/scripts/arg_printer.py" target="_blank">arg_printer</a> 脚本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>Cmd</span><span>) </span><span>run_pyscript</span><span> </span><span>examples/scripts/arg_printer.py</span><span> </span><span>foo</span><span> </span><span>bar</span><span> </span><span>baz</span></div></div><div><div><div>2</div></div><div><span>Running</span><span> </span><span>Python</span><span> </span><span>script</span><span> </span><span>'arg_printer.py'</span><span> </span><span>which</span><span> </span><span>was</span><span> </span><span>called</span><span> </span><span>with</span><span> </span><span>3</span><span> </span><span>arguments</span></div></div><div><div><div>3</div></div><div><span>arg</span><span> </span><span>1:</span><span> </span><span>'foo'</span></div></div><div><div><div>4</div></div><div><span>arg</span><span> </span><span>2:</span><span> </span><span>'bar'</span></div></div><div><div><div>5</div></div><div><span>arg</span><span> </span><span>3:</span><span> </span><span>'baz'</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>IPython（可选）<a href="#ipython可选"><span>#</span></a></h2><p><strong>如果</strong>系统中安装了 <a href="http://ipython.readthedocs.io" target="_blank">IPython</a>，<strong>并且</strong> <code>cmd2.Cmd</code> 类在实例化时传入 <code>include_ipy=True</code>，则可选的 <code>ipy</code> 命令将启动一个交互式 IPython shell：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>, </span><span>include_ipy</span><span>=</span><span>True</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>ipy</code> 命令进入一个交互式 <a href="http://ipython.readthedocs.io" target="_blank">IPython</a> 会话。与交互式 Python 会话类似，如果 <code>self.self_in_py</code> 为 <code>True</code>，此 shell 可以通过 <code>self</code> 访问你的应用实例，并且通过 <code>self</code> 对应用所做的任何更改都将持久存在。但是，在 <code>ipy</code> shell 中创建的任何局部或全局变量不会在 CLI 环境中持久存在。</p><p>同样，与交互式 Python 会话一样，<code>ipy</code> shell 可以访问 <code>self.py_locals</code> 的内容，并可以使用 <code>app</code> 对象（或你的自定义名称）回调应用。</p><p><a href="http://ipython.readthedocs.io" target="_blank">IPython</a> 提供了许多优势，包括：</p><blockquote><ul>
<li>全面的对象自省</li>
<li>使用 <code>?</code> 获取对象帮助</li>
<li>可扩展的 Tab 补全，默认支持 Python 变量和关键字的补全</li>
<li>优秀的内置 <a href="https://pypi.org/project/ipdb/" target="_blank">ipdb</a> 调试器</li>
</ul></blockquote><p>对象自省和 Tab 补全使 IPython 在调试以及交互式实验和数据分析方面特别高效。</p></section></section>
<section><h1>生成输出<a href="#生成输出"><span>#</span></a></h1><p>标准 <code>cmd</code> 应用可以使用以下任一方法产生输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span>(</span><span>"Greetings, Professor Falken."</span><span>, </span><span>file</span><span>=</span><span>self</span><span>.stdout)</span></div></div><div><div><div>2</div></div><div><span>self</span><span><span>.stdout.</span><span>write</span><span>(</span></span><span>"Shall we play a game?</span><span>\n</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>虽然你可以直接将输出发送到 <code>sys.stdout</code>，但 <code>cmd2.Cmd</code> 可以使用 <code>stdin</code> 和 <code>stdout</code> 变量进行初始化，它们会存储为 <code>self.stdin</code> 和 <code>self.stdout</code>。通过每次产生输出时使用这些变量，你可以通过更改类的初始化方式来轻松改变所有输出的目标位置。</p><p><code>cmd2.Cmd</code> 以多种便捷方式扩展了这种方法。参阅<a href="../prompt_redirection/#%E8%BE%93%E5%87%BA%E9%87%8D%E5%AE%9A%E5%90%91%E4%B8%8E%E7%AE%A1%E9%81%93">输出重定向和管道</a>了解用户如何更改命令输出的发送位置。为了让这些功能正常工作，你生成的输出必须发送到 <code>self.stdout</code>。你可以使用上述方法，一切都会正常工作。<code>cmd2.Cmd</code> 还包含许多输出相关的方法，你可以用来增强应用产生的输出。</p><p>由于 <code>cmd2</code> 依赖于 <a href="https://github.com/Textualize/rich" target="_blank">rich</a> 库，以下 <code>cmd2.Cmd</code> 输出方法可以原生渲染 rich 的<a href="https://rich.readthedocs.io/en/latest/protocol.html" target="_blank">可渲染对象</a>，实现美观复杂的输出：</p><ul>
<li><code>poutput</code></li>
<li><code>perror</code></li>
<li><code>psuccess</code></li>
<li><code>pwarning</code></li>
<li><code>pfeedback</code></li>
<li><code>ppaged</code></li>
</ul><div><div><div></div><div>Tip</div></div><div><p><strong>高级输出自定义</strong>
上述每个方法都接受额外的可选参数来帮助控制输出格式：</p><ul>
<li><code>sep</code>：在打印文本之间写入的字符串，默认为 ” ”</li>
<li><code>end</code>：在打印文本末尾写入的字符串，默认为换行符</li>
<li><code>style</code>：应用于输出的可选样式</li>
<li><code>soft_wrap</code>：启用软换行模式。如果为 True，文本行不会被自动换行或裁剪以适应终端宽度。默认为 True</li>
<li><code>justify</code>：对齐方式（“left”、“center”、“right”、“full”）。默认为 None</li>
<li><code>emoji</code>：如果为 True，Rich 会将 emoji 代码（如 😃）替换为对应的 Unicode 字符。默认为 False</li>
<li><code>markup</code>：如果为 True，Rich 会将带有标签的字符串（如 <code>[bold]hello[/bold]</code>）解释为带样式的输出。默认为 False</li>
<li><code>highlight</code>：如果为 True，Rich 会自动对字符串中的元素应用高亮，如常见的 Python 数据类型（数字、布尔值或 None）</li>
<li><code>rich_print_kwargs</code>：传递给 <code>console.print()</code> 的可选额外关键字参数</li>
<li><code>kwargs</code>：用于支持扩展 print 方法的任意关键字参数，不会传递给 <code>console.print()</code></li>
</ul></div></div><section><h2>普通输出<a href="#普通输出"><span>#</span></a></h2><p><code>poutput</code> 方法类似于 Python 内置的 <a href="https://docs.python.org/3/library/functions.html#print" target="_blank">print</a> 函数。<code>poutput</code> 添加了一些便利功能：</p><ol>
<li>由于用户可以将输出管道传递给 shell 命令，它会捕获 <code>BrokenPipeError</code> 并将 <code>self.broken_pipe_warning</code> 的内容输出到 <code>stderr</code>。<code>self.broken_pipe_warning</code> 默认为空字符串，所以此方法只会吞掉异常。如果你想显示错误消息，请在初始化 <code>cmd2.Cmd</code> 时将其放入 <code>self.broken_pipe_warning</code> 中。</li>
<li>它会检查并遵守 <a href="../settings_plugins/#allow_style">allow_style</a> 设置。详见下方<a href="#%E5%BD%A9%E8%89%B2%E8%BE%93%E5%87%BA">彩色输出</a>。</li>
<li>它允许打印任意 rich 可渲染对象，可以实现视觉上非常复杂的效果。</li>
</ol><p>以下是一个展示此方法用法的简单命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""A simple command showing how poutput() works"""</span></div></div><div><div><div>3</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(args)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>错误消息<a href="#错误消息"><span>#</span></a></h2><p>当程序中发生错误时，你可以通过调用 <code>perror</code> 方法在 <code>sys.stderr</code> 上显示它。默认情况下，此方法会将 <code>Cmd2Style.ERROR</code> 应用于输出。</p></section><section><h2>警告消息<a href="#警告消息"><span>#</span></a></h2><p><code>pwarning</code> 与 <code>cmd2.Cmd.perror</code> 类似，但会将 <code>Cmd2Style.WARNING</code> 应用于输出。</p></section><section><h2>反馈信息<a href="#反馈信息"><span>#</span></a></h2><p>你可能需要向用户显示不属于生成输出的信息。这可能是调试信息或长时间运行命令的进度信息。它不是输出，不是错误消息，而是状态信息。如果你使用 <a href="../settings_plugins/#timing">Timing</a> 设置，命令运行耗时的输出将以无样式的文本输出到 <code>stderr</code>。你可以通过将 <code>style=None</code> 传递给 <code>perror</code> 方法来产生此类输出。</p><p>如果 <a href="../settings_plugins/#quiet">quiet</a> 设置为 <code>True</code>，则调用 <code>cmd2.Cmd.pfeedback</code> 不会产生任何输出。如果 <code>quiet</code> 为 <code>False</code>，<code>pfeedback</code> 方法将输出发送到 <code>stdout</code>。因此，<code>pfeedback</code> 适用于你希望在 <code>quiet</code> 为 <code>True</code> 时能够静音的非必要输出。</p></section><section><h2>异常<a href="#异常"><span>#</span></a></h2><p>如果你的应用捕获了异常并希望向用户显示异常信息，<code>pexcept</code> 方法可以提供帮助。默认行为是只显示异常中包含的消息。但是，如果 <a href="../settings_plugins/#debug">debug</a> 设置为 <code>True</code>，则会显示整个堆栈跟踪。</p></section><section><h2>分页输出<a href="#分页输出"><span>#</span></a></h2><p>如果你知道将要生成大量输出，你可能希望以用户可以前后滚动的方式显示它。如果你将所有要显示的输出传递给 <code>ppaged</code> 的单次调用，它将被管道传递给操作系统适当的 shell 命令来分页输出。在 Windows 上，输出被管道传递给 <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/more" target="_blank">more</a>；在 macOS 和 Linux 等类 Unix 操作系统上，输出被管道传递给 <a href="https://man7.org/linux/man-pages/man1/less.1.html" target="_blank">less</a>。</p></section><section><h2>彩色输出<a href="#彩色输出"><span>#</span></a></h2><p>你可以在输出中添加自己的 <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors" target="_blank">ANSI 转义序列</a>来指示终端更改前景色和背景色。</p><p><code>cmd2</code> 提供了许多便捷函数和类来为文本添加颜色和其他样式。这些都基于 <a href="https://github.com/Textualize/rich" target="_blank">rich</a>，文档在以下章节中：</p><ul>
<li><code>cmd2.colors</code></li>
<li><code>cmd2.rich_utils</code></li>
<li><code>cmd2.string_utils</code></li>
</ul><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/color.py" target="_blank">color.py</a> 示例演示了 <code>cmd2</code> 应用中所有可用的颜色。</p><section><h3>自定义主题<a href="#自定义主题"><span>#</span></a></h3><p><code>cmd2</code> 使用 rich 的 <a href="https://rich.readthedocs.io/en/stable/reference/theme.html" target="_blank">Theme</a> 对象来定义各种 UI 元素的样式。你可以使用 <code>cmd2.theme.update_theme</code> 定义自己的自定义主题。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py" target="_blank">rich_theme.py</a> 示例了解更多信息。</p><p>在向输出添加所需的转义序列后，你应该使用以下方法之一来向用户呈现输出：</p><ul>
<li><code>cmd2.Cmd.poutput</code></li>
<li><code>cmd2.Cmd.perror</code></li>
<li><code>cmd2.Cmd.pwarning</code></li>
<li><code>cmd2.Cmd.pexcept</code></li>
<li><code>cmd2.Cmd.pfeedback</code></li>
<li><code>cmd2.Cmd.ppaged</code></li>
</ul><p>这些方法都遵守 <a href="../settings_plugins/#allow_style">allow_style</a> 设置，用户可以修改该设置来控制这些转义码是否传递到终端。</p></section></section><section><h2>文本对齐<a href="#文本对齐"><span>#</span></a></h2><p>如果你想生成在指定宽度或终端宽度内左对齐、居中或右对齐的输出，以下函数可以提供帮助：</p><ul>
<li><code>cmd2.string_utils.align_left</code></li>
<li><code>cmd2.string_utils.align_center</code></li>
<li><code>cmd2.string_utils.align_right</code></li>
</ul><p>这些函数与 Python 的字符串对齐函数不同，它们支持显示宽度大于 1 的字符。此外，ANSI 样式序列会被安全忽略，不计入显示宽度。这意味着支持彩色文本。如果文本包含换行符，则每行独立对齐。</p><div><div><div></div><div>Tip</div></div><div><p><strong>高级对齐自定义</strong>
你也可以在调用 <code>cmd2</code> 的 print 方法时使用 <code>justify</code> 参数来控制输出对齐方式。</p></div></div></section><section><h2>多列输出<a href="#多列输出"><span>#</span></a></h2><p>在生成多列输出时，你通常需要计算每个项目的宽度以便用空格适当填充。然而，有些 Unicode 字符占用 2 个单元格，而另一些占用 0 个。更复杂的是，你可能在输出中包含了 ANSI 转义序列来在终端上生成颜色。</p><p><code>cmd2.string_utils.str_width</code> 函数解决了这两个问题。给它传递一个字符串，无论它包含哪些 Unicode 字符和 ANSI 文本样式转义序列，它都会告诉你该字符串在屏幕上打印时会占用多少个字符。</p></section></section>
<section><h1>帮助<a href="#帮助"><span>#</span></a></h1><p>根据我们的经验，终端用户很少阅读文档，无论文档多么高质量或有用。因此，在应用中提供良好的内置帮助非常重要。幸运的是，<code>cmd2</code> 使这变得简单。</p><section><h2>获取帮助<a href="#获取帮助"><span>#</span></a></h2><p><code>cmd2</code> 使终端用户可以通过内置的 <code>help</code> 命令轻松获取帮助。单独的 <code>help</code> 命令显示可用命令的列表：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) help</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>Documented Commands</span></div></div><div><div><div>4</div></div><div><span><span> </span></span><span>alias  help     ipy    py    run_pyscript  set    shortcuts</span></div></div><div><div><div>5</div></div><div><span><span> </span></span><span>edit   history  macro  quit  run_script    shell</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>help</code> 命令也可用于提供特定命令的详细帮助：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) help quit</span></div></div><div><div><div>2</div></div><div><span>Usage: quit [-h]</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>Exit this application.</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>Optional Arguments:</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>-h, --help  show this help message and exit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>提供帮助<a href="#提供帮助"><span>#</span></a></h2><p><code>cmd2</code> 使 <code>cmd2</code> 应用的开发者可以轻松提供帮助。默认情况下，命令的帮助是定义命令的 <code>do_*</code> 方法的文档字符串——例如，对于命令 <strong>foo</strong>，该命令通过定义 <code>do_foo</code> 方法实现，该方法的文档字符串就是帮助内容。</p><p>对于使用 <code>@with_argparser</code> 装饰器解析参数的命令，帮助由 <code>argparse</code> 提供。详见<a href="../argument_processing/#help-messages">帮助消息</a>。</p><p>偶尔可能会出现不寻常的情况，提供静态帮助文本不够好，你希望在命令的帮助文本中提供动态信息。为满足此需求，如果定义了与 <code>do_foo</code> 方法匹配的 <code>help_foo</code> 方法，则该方法将用于提供命令 <strong>foo</strong> 的帮助。此动态帮助仅适用于不使用 <code>argparse</code> 装饰器的命令，因为我们不希望 <code>help cmd</code> 和 <code>cmd -h</code> 的输出不同。</p></section><section><h2>命令分类<a href="#命令分类"><span>#</span></a></h2><p>在 <code>cmd2</code> 中，<code>help</code> 命令将其输出组织为分类。每个命令属于一个分类，显示由 <code>DEFAULT_CATEGORY</code> 类变量驱动。</p><p>有 3 种指定命令分类的方法：</p><ol>
<li>使用 <code>DEFAULT_CATEGORY</code> 类变量（自动）</li>
<li>使用 <code>@with_category</code> 装饰器（手动）</li>
<li>使用 <code>categorize()</code> 函数（手动）</li>
</ol><section><h3>自动分类<a href="#自动分类"><span>#</span></a></h3><p>对命令进行分类最有效的方式是在 <code>Cmd</code> 或 <code>CommandSet</code> 类中定义 <code>DEFAULT_CATEGORY</code> 类变量。在该类中定义的任何没有显式分类覆盖的命令将自动归入此分类。</p><p>默认情况下，<code>cmd2.Cmd</code> 将其 <code>DEFAULT_CATEGORY</code> 定义为 <code>"Cmd2 Commands"</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span># All commands defined in this class will be grouped here</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Application Commands"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>do_echo</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>):</span></div></div><div><div><div>6</div></div><div><span>        </span><span>"""Echo command"""</span></div></div><div><div><div>7</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(arg)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这也适用于<a href="../modular_commands/#%E5%91%BD%E4%BB%A4%E9%9B%86">命令集</a>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>Plugin</span><span>(</span><span>cmd2</span><span>.</span><span>CommandSet</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Plugin Commands"</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>do_plugin_cmd</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span>"""Plugin command"""</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Plugin"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用继承时，<code>cmd2</code> 使用命令实际定义所在类的 <code>DEFAULT_CATEGORY</code>。这意味着内置命令（如 <code>help</code>、<code>history</code> 和 <code>quit</code>）保留在 <code>"Cmd2 Commands"</code> 分类中，而你的命令移到你的自定义分类中。</p><p>如果你想重命名内置分类本身，可以在 <code>Cmd</code> 子类中通过在类级别重新赋值 <code>cmd2.Cmd.DEFAULT_CATEGORY</code> 来实现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span># Rename the framework's built-in category</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>cmd2.Cmd.</span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Shell Commands"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span># Set the category for your own commands</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Application Commands"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>有关此功能的完整演示，请参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/default_categories.py" target="_blank">default_categories.py</a> 示例。</p></section><section><h3>手动分类<a href="#手动分类"><span>#</span></a></h3><p>如果需要将单个命令移到与类默认不同的分类中，可以使用 <code>@with_category</code> 装饰器或 <code>categorize()</code> 函数。这些手动设置始终优先于 <code>DEFAULT_CATEGORY</code>。</p><p>使用 <code>@with_category</code> 装饰器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@with_category</span><span>(</span><span>"Connecting"</span><span>)</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>do_which</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"""Which command"""</span></div></div><div><div><div>4</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Which"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用 <code>categorize()</code> 函数：</p><p>可以对单个函数调用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_connect</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""Connect command"""</span></div></div><div><div><div>3</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Connect"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># Tag the above command functions under the category Connecting</span></div></div><div><div><div>7</div></div><div><span><span>categorize</span><span>(do_connect, </span><span>CMD_CAT_CONNECTING</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者对函数的可迭代容器调用：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_undeploy</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""Undeploy command"""</span></div></div><div><div><div>3</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Undeploy"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>def</span><span> </span><span>do_stop</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>7</div></div><div><span>    </span><span>"""Stop command"""</span></div></div><div><div><div>8</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Stop"</span><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>do_findleakers</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>):</span></div></div><div><div><div>12</div></div><div><span>    </span><span>"""Find Leakers command"""</span></div></div><div><div><div>13</div></div><div><span>    </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Find Leakers"</span><span>)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span># Tag the above command functions under the category Application Management</span></div></div><div><div><div>17</div></div><div><span><span>categorize</span><span>((do_undeploy, do_stop, do_findleakers), </span><span>CMD_CAT_APP_MGMT</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>help</code> 命令还有一个详细选项（<code>help -v</code> 或 <code>help --verbose</code>），它将帮助分类与每个命令的帮助消息结合在一起：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Application Management</span></div></div><div><div><div>2</div></div><div><span>─────────────────────────────────────</span></div></div><div><div><div>3</div></div><div><span>Name          Description</span></div></div><div><div><div>4</div></div><div><span>─────────────────────────────────────</span></div></div><div><div><div>5</div></div><div><span>deploy        Deploy command.</span></div></div><div><div><div>6</div></div><div><span>expire        Expire command.</span></div></div><div><div><div>7</div></div><div><span>findleakers   Find Leakers command.</span></div></div><div><div><div>8</div></div><div><span>list          List command.</span></div></div><div><div><div>9</div></div><div><span>redeploy      Redeploy command.</span></div></div><div><div><div>10</div></div><div><span>restart       Restart command.</span></div></div><div><div><div>11</div></div><div><span>sessions      Sessions command.</span></div></div><div><div><div>12</div></div><div><span>start         Start command.</span></div></div><div><div><div>13</div></div><div><span>stop          Stop command.</span></div></div><div><div><div>14</div></div><div><span>undeploy      Undeploy command.</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>Command Management</span></div></div><div><div><div>18</div></div><div><span>─────────────────────────────────────────────────────────────────</span></div></div><div><div><div>19</div></div><div><span>Name               Description</span></div></div><div><div><div>20</div></div><div><span>─────────────────────────────────────────────────────────────────</span></div></div><div><div><div>21</div></div><div><span>disable_commands   Disable the Application Management commands.</span></div></div><div><div><div>22</div></div><div><span>enable_commands    Enable the Application Management commands.</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>Connecting</span></div></div><div><div><div>26</div></div><div><span>────────────────────────────</span></div></div><div><div><div>27</div></div><div><span>Name      Description</span></div></div><div><div><div>28</div></div><div><span>────────────────────────────</span></div></div><div><div><div>29</div></div><div><span>connect   Connect command.</span></div></div><div><div><div>30</div></div><div><span>which     Which command.</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>Server Information</span></div></div><div><div><div>34</div></div><div><span>─────────────────────────────────────────────────────────────────────────────────────────────────</span></div></div><div><div><div>35</div></div><div><span>Name                  Description</span></div></div><div><div><div>36</div></div><div><span>─────────────────────────────────────────────────────────────────────────────────────────────────</span></div></div><div><div><div>37</div></div><div><span>resources             Resources command.</span></div></div><div><div><div>38</div></div><div><span>serverinfo            Server Info command.</span></div></div><div><div><div>39</div></div><div><span>sslconnectorciphers   SSL Connector Ciphers command is an example of a command that contains</span></div></div><div><div><div>40</div></div><div><span><span>                    </span></span><span>multiple lines of help information for the user. Each line of help in a</span></div></div><div><div><div>41</div></div><div><span><span>                    </span></span><span>contiguous set of lines will be printed and aligned in the verbose output</span></div></div><div><div><div>42</div></div><div><span><span>                    </span></span><span>provided with 'help --verbose'.</span></div></div><div><div><div>43</div></div><div><span>status                Status command.</span></div></div><div><div><div>44</div></div><div><span>thread_dump           Thread Dump command.</span></div></div><div><div><div>45</div></div><div><span>vminfo                VM Info command.</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>Other</span></div></div><div><div><div>49</div></div><div><span>─────────────────────────────────────────────────────────────────────────────────────────</span></div></div><div><div><div>50</div></div><div><span>Name           Description</span></div></div><div><div><div>51</div></div><div><span>─────────────────────────────────────────────────────────────────────────────────────────</span></div></div><div><div><div>52</div></div><div><span>alias          Manage aliases.</span></div></div><div><div><div>53</div></div><div><span>config         Config command.</span></div></div><div><div><div>54</div></div><div><span>edit           Run a text editor and optionally open a file with it.</span></div></div><div><div><div>55</div></div><div><span>help           List available commands or provide detailed help for a specific command.</span></div></div><div><div><div>56</div></div><div><span>history        View, run, edit, save, or clear previously entered commands.</span></div></div><div><div><div>57</div></div><div><span>macro          Manage macros.</span></div></div><div><div><div>58</div></div><div><span>quit           Exit this application.</span></div></div><div><div><div>59</div></div><div><span>run_pyscript   Run Python script within this application's environment.</span></div></div><div><div><div>60</div></div><div><span>run_script     Run text script.</span></div></div><div><div><div>61</div></div><div><span>set            Set a settable parameter or show current settings of parameters.</span></div></div><div><div><div>62</div></div><div><span>shell          Execute a command as if at the OS prompt.</span></div></div><div><div><div>63</div></div><div><span>shortcuts      List available shortcuts.</span></div></div><div><div><div>64</div></div><div><span>version        Version command.</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>When called with the <code>-v</code> flag for verbose help, the one-line description for each command is
provided by the first line of the docstring for that command’s associated <code>do_*</code> method.</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/history/</id>
      <title type="text">Python cmd2：命令历史功能详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/history/"/>
      <summary type="text">详细介绍 cmd2 的命令历史功能，包括开发者接口和用户操作方式，涵盖历史记录的保存、搜索、编辑和脚本导出等。</summary>
      <content type="html"><![CDATA[<section><h1>命令历史<a href="#命令历史"><span>#</span></a></h1><section><h2>开发者指南<a href="#开发者指南"><span>#</span></a></h2><p>此前，<code>cmd2</code> 依赖 GNU Readline 库来处理命令历史。从 4.0.0 版本开始，<code>cmd2</code> 已迁移到 <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">prompt-toolkit</a>，用于所有输入和历史记录的处理。</p><p><code>cmd2.Cmd</code> 使用 <code>prompt-toolkit</code> 提供用户熟悉的命令行历史功能，同时维护自己的数据结构来存储用户输入的所有命令历史。当类被初始化时，它会创建一个 <code>cmd2.history.History</code> 类（<code>list</code> 的子类）的实例作为 <code>cmd2.Cmd.history</code>。</p><p>每次命令被执行时（这个过程比较复杂，具体时机请参阅<a href="../hooks/#%E5%91%BD%E4%BB%A4%E5%A4%84%E7%90%86%E5%BE%AA%E7%8E%AF">命令处理循环</a>），解析后的 <code>cmd2.Statement</code> 对象会被追加到 <code>cmd2.Cmd.history</code> 中。</p><p><code>cmd2</code> 通过 <code>cmd2.Cmd.__init__</code> 的可选参数提供了将历史记录持久化的功能。如果你在 <code>persistent_history_file</code> 参数中传入一个文件名，<code>cmd2.Cmd.history</code> 的内容将以压缩 JSON 格式写入该历史文件。我们选择这种格式而非纯文本，是为了保留每条命令的完整 <code>cmd2.Statement</code> 对象。</p><div><div><div></div><div>Note</div></div><div><p><code>prompt-toolkit</code> 会保存你输入的所有内容，无论是否为有效命令。而 <code>cmd2</code> 仅在命令解析成功且为有效命令时，才会将输入保存到内部历史记录中。这种设计是有意为之的，因为历史记录的内容可以保存为脚本文件，也可以重新执行。不保存无效输入可以减少重新执行时出现的意外错误。</p><p>然而，这种设计会导致当你输入无效命令时，<code>prompt-toolkit</code> 历史记录与 <code>cmd2</code> 历史记录之间出现不一致：无效命令会被保存到 <code>prompt-toolkit</code> 的历史记录中，但不会保存到 <code>cmd2</code> 的历史记录中。</p></div></div><p><code>cmd2.Cmd.history</code> 属性、<code>cmd2.history.History</code> 类和 <code>cmd2.history.HistoryItem</code> 类都是 <code>cmd2.Cmd</code> 公共 API 的一部分。你可以使用这些类来实现自己的 <code>history</code> 命令（关于内置 <code>history</code> 命令的工作方式，请参阅下文文档）。</p></section><section><h2>用户指南<a href="#用户指南"><span>#</span></a></h2><p>你可以使用 ↑ 和 ↓ 方向键在之前输入的命令历史中导航。</p><p>你可以按 <code>Control-p</code> 移动到上一条输入的命令，按 <code>Control-n</code> 移动到下一条命令。你还可以使用 <code>Control-r</code> 搜索命令历史。</p><p>默认情况下，<code>prompt-toolkit</code> 提供 Emacs 风格的快捷键绑定，这对 GNU Readline 库的用户来说会很熟悉。你可以参考 <a href="http://readline.kablamo.org/emacs.html" target="_blank">readline 速查表</a>或深入阅读 <a href="https://python-prompt-toolkit.readthedocs.io/en/stable/pages/advanced_topics/key_bindings.html" target="_blank">Prompt Toolkit 用户手册</a>获取所有细节，包括自定义快捷键绑定的说明。</p><p><code>cmd2</code> 通过 <code>history</code> 命令提供了第三种历史访问方式。每次用户输入命令时，<code>cmd2</code> 都会保存该输入。<code>history</code> 命令让你可以对这些已保存的输入进行各种有趣的操作。以下示例均假设你已经输入了以下命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) alias create one !echo one</span></div></div><div><div><div>2</div></div><div><span>Alias 'one' created</span></div></div><div><div><div>3</div></div><div><span>(Cmd) alias create two !echo two</span></div></div><div><div><div>4</div></div><div><span>Alias 'two' created</span></div></div><div><div><div>5</div></div><div><span>(Cmd) alias create three !echo three</span></div></div><div><div><div>6</div></div><div><span>Alias 'three' created</span></div></div><div><div><div>7</div></div><div><span>(Cmd) alias create four !echo four</span></div></div><div><div><div>8</div></div><div><span>Alias 'four' created</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>history</code> 命令最简单的形式是显示之前输入的命令。不带任何参数时，它会显示所有之前输入的命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create one !echo one</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>4  alias create four !echo four</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果提供一个正整数作为参数，则只显示指定的命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history 4</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>4  alias create four !echo four</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果提供一个负整数 <em>N</em> 作为参数，则显示倒数第 <em>N</em> 条命令。例如，<code>-1</code> 会显示你最后输入的命令，<code>-2</code> 会显示倒数第二条命令，以此类推：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -2</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可以使用类似的机制来显示一个范围内的命令。只需提供两个用 <code>..</code> 或 <code>:</code> 分隔的命令编号，你就能看到这两个编号之间（包含两端）的所有命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history 1:3</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create one !echo one</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果省略第一个编号，将从头开始；如果省略最后一个编号，将继续到末尾：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history :2</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create one !echo one</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div><div><div><div>4</div></div><div><span>(Cmd) history 2:</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>4  alias create four !echo four</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你想显示最后输入的三条命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -- -3:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>4  alias create four !echo four</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意这里的双横线 <code>--</code>。这是必需的，因为 history 命令使用 <code>argparse</code> 来解析命令行参数。正如 <a href="https://docs.python.org/3/library/argparse.html" target="_blank">argparse 文档</a>中所述，<code>-3:</code> 会被解析为选项而非参数：</p><blockquote><p>如果你的位置参数必须以 <code>-</code> 开头且看起来不像负数，你可以插入伪参数 <code>--</code>，它会告诉 <code>parse_args()</code> 在此之后的所有内容都是位置参数。</p></blockquote><p>不存在第零条命令，所以不要去请求它。如果你是 Python 程序员，你可能已经注意到这很像列表和数组的切片语法。确实如此，唯一的区别是历史记录的第一条命令编号为 1，而 Python 数组的第一个元素索引为 0。</p><p>除了通过编号选择历史命令，你还可以搜索它们。可以使用简单的字符串搜索：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history two</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>2  alias create two !echo two</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者将正则表达式放在斜杠之间进行正则搜索：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history '/te\ +th/'</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>3  alias create three !echo three</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你的正则表达式包含 <code>argparse</code> 会特殊处理的字符（如破折号或加号），还需要将正则表达式用引号括起来。</p><p>这些功能听起来很棒，但如果我们只能显示历史命令，这么多选择方式是不是有点多余？实际上，显示历史命令只是开始。history 命令还能执行许多其他操作：</p><ul>
<li>运行之前输入的命令</li>
<li>将之前输入的命令保存到文本文件</li>
<li>在你喜欢的文本编辑器中打开之前输入的命令</li>
<li>运行之前输入的命令，并将命令及其输出保存到文本文件</li>
<li>清除已输入的命令历史</li>
</ul><p>每种操作都通过命令行选项来调用。<code>-r</code> 或 <code>--run</code> 选项用于运行一条或多条之前输入的命令。运行第 1 条命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history --run 1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>重新运行最后两条命令（再次使用双横线来阻止 argparse 查找选项）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -r -- -2:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>假设你想重新运行一些之前输入的命令，但在运行前想做一些修改。当你使用 <code>-e</code> 或 <code>--edit</code> 选项时，<code>history</code> 会将选中的命令写入一个文本文件，并用文本编辑器打开该文件。你可以进行任何修改、添加或删除操作。当你退出文本编辑器后，文件中的所有命令都会被执行。要编辑并重新运行第 2-4 条命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history --edit 2:4</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你想将命令保存到文本文件，但不编辑和重新运行它们，可以使用 <code>-o</code> 或 <code>--output-file</code> 选项。这是创建<a href="../scripting/">脚本</a>的好方法，脚本可以通过 <code>run_script</code> 命令执行。要将本次会话中输入的前 5 条命令保存到文本文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history :5 -o history.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>history 命令可以执行的最后一个操作是使用 <code>-c</code> 或 <code>--clear</code> 来清除命令历史：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -c</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>除了这五种操作外，<code>history</code> 命令还有一些选项来控制输出格式。不带参数时，<code>history</code> 命令会在每条命令前显示命令编号。这在屏幕显示历史记录时很有用，因为它为你提供了一个便捷的参考标识来识别之前输入的命令。但是，在创建脚本时，命令编号会阻止脚本正常加载。<code>-s</code> 或 <code>--script</code> 选项指示 <code>history</code> 命令不显示行号。该选项会被 <code>--output-file</code> 和 <code>--edit</code> 选项自动设置。如果你想将带行号的历史命令输出到文件，可以使用输出重定向：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history 1:4 &gt; history.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你也可以单独使用 <code>-s</code> 或 <code>--script</code> 来在屏幕上显示不带行号的历史命令，以便复制到剪贴板：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -s 1:3</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>cmd2</code> 支持别名和宏，它们允许你将一个简短、方便的输入字符串替换为更长的字符串。假设我们创建了这样一个别名，然后使用它：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) alias create ls shell ls -aF</span></div></div><div><div><div>2</div></div><div><span>Alias 'ls' created</span></div></div><div><div><div>3</div></div><div><span>(Cmd) ls -d h*</span></div></div><div><div><div>4</div></div><div><span>history.txt     htmlcov/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>默认情况下，<code>history</code> 命令显示的正是我们输入的内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create ls shell ls -aF</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  ls -d h*</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>有两种方式可以修改显示内容，让你看到别名和宏展开后的结果。第一种是使用 <code>-x</code> 或 <code>--expanded</code>。这些选项显示展开后的命令而非输入的命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -x</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create ls shell ls -aF</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  shell ls -aF -d h*</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你想同时看到输入的命令和展开后的命令，使用 <code>-v</code> 或 <code>--verbose</code> 选项：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) history -v</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1  alias create ls shell ls -aF</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2  ls -d h*</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>2x shell ls -aF -d h*</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果输入的命令没有展开操作，则照常显示。但如果由于宏和别名的展开导致内容发生了变化，则输入的命令会带有编号显示，展开后的命令会带有编号加 <code>x</code> 后缀显示。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/hooks/</id>
      <title type="text">Python cmd2：钩子机制详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/hooks/"/>
      <summary type="text">详细介绍 cmd2 的钩子机制，包括应用生命周期钩子、命令处理循环中的各类钩子（解析后、命令前、命令后、命令终结）及其使用方法。</summary>
      <content type="html"><![CDATA[<section><h1>钩子<a href="#钩子"><span>#</span></a></h1><p>启动 <code>cmd2</code> 应用的典型方式如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span># customized attributes and methods here</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>'__main__'</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>App</span><span>()</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>app.</span><span>cmdloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>有多个现成的方法和属性可供你调整，以控制应用在命令处理循环之前、期间和之后的整体行为。</p><section><h2>应用生命周期钩子<a href="#应用生命周期钩子"><span>#</span></a></h2><p>你可以通过在 <code>cmd2.Cmd.__init__</code> 的 <code>startup_script</code> 参数中传入脚本文件名，在初始化时运行脚本。</p><p>你还可以注册在命令循环开始时调用的方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>    </span><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>        </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>            </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>            </span><span>self</span><span><span>.</span><span>register_preloop_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>        </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>7</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"before the loop begins"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>为了保持与 <code>cmd.Cmd</code> 的向后兼容性，在所有已注册的 preloop 钩子被调用之后，<code>cmd2.Cmd.preloop</code> 方法会被调用。</p><p>类似的方式允许你注册在命令循环结束后调用的函数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>    </span><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>        </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>            </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>            </span><span>self</span><span><span>.</span><span>register_postloop_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>        </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>7</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"after the loop ends"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>为了保持与 <code>cmd.Cmd</code> 的向后兼容性，在所有已注册的 postloop 钩子被调用之后，<code>cmd2.Cmd.postloop</code> 方法会被调用。</p><p>Preloop 和 postloop 钩子方法不接收任何参数，任何返回值都会被忽略。</p><p>注册钩子而非重写方法的方式允许多个钩子在命令循环开始或结束时被调用。插件作者应仔细完整地阅读本页以了解编写钩子的最佳实践。</p></section><section><h2>应用生命周期属性<a href="#应用生命周期属性"><span>#</span></a></h2><p><code>cmd2.Cmd</code> 上有许多属性会影响进入或运行命令循环时的应用行为：</p><ul>
<li><code>cmd2.Cmd.intro</code> - 如果提供，它会作为启动横幅在应用启动时打印一次，在 <code>cmd2.Cmd.preloop</code> 被调用之后</li>
<li><code>cmd2.Cmd.prompt</code> - 详见<a href="../prompt_redirection/#%E6%8F%90%E7%A4%BA%E7%AC%A6">提示符</a></li>
<li><code>cmd2.Cmd.continuation_prompt</code> - 在<a href="../app_setup/#%E5%A4%9A%E8%A1%8C%E5%91%BD%E4%BB%A4">多行命令</a>的第 2 行及后续行请求输入时显示的提示符</li>
<li><code>cmd2.Cmd.echo</code> - 如果为 <code>True</code>，将提示符和命令写入输出流</li>
</ul><p>此外，<code>cmd2.Cmd.__init__</code> 的几个参数也会影响命令循环行为：</p><ul>
<li><code>allow_cli_args</code> - 允许在操作系统命令行上指定命令，这些命令会在命令处理循环开始之前执行</li>
<li><code>startup_script</code> - 在初始化时运行脚本。详见<a href="../scripting/">脚本</a></li>
</ul></section><section><h2>命令处理循环<a href="#命令处理循环"><span>#</span></a></h2><p>当你调用 <code>cmd2.Cmd.cmdloop</code> 时，以下事件序列会重复执行直到应用退出：</p><ol>
<li>启动 <code>prompt-toolkit</code> 事件循环</li>
<li>调用 <code>cmd2.Cmd.pre_prompt</code>，用于在事件循环启动后但提示符显示之前执行任何行为</li>
<li>输出提示符</li>
<li>接受用户输入</li>
<li>将用户输入解析为 <code>cmd2.Statement</code> 对象</li>
<li>调用通过 <code>cmd2.Cmd.register_postparsing_hook</code> 注册的方法</li>
<li>如果用户请求且允许，重定向输出</li>
<li>启动计时器</li>
<li>调用通过 <code>cmd2.Cmd.register_precmd_hook</code> 注册的方法</li>
<li>调用 <code>cmd2.Cmd.precmd</code> - 为了与 <code>cmd.Cmd</code> 向后兼容</li>
<li>将语句添加到<a href="../history/">命令历史</a></li>
<li>调用 <code>do_command</code> 方法</li>
<li>调用通过 <code>cmd2.Cmd.register_postcmd_hook</code> 注册的方法</li>
<li>调用 <code>cmd2.Cmd.postcmd</code> - 为了与 <code>cmd.Cmd</code> 向后兼容</li>
<li>停止计时器并显示经过的时间</li>
<li>如果输出被重定向，停止重定向</li>
<li>调用通过 <code>cmd2.Cmd.register_cmdfinalization_hook</code> 注册的方法</li>
</ol><p>通过注册钩子方法，你可以在命令处理循环的多个步骤中运行代码并控制流程。请注意，插件也会使用这些钩子，因此可能会有不属于你应用代码的代码在运行。为某个钩子注册的方法按注册顺序调用。你可以多次注册同一个函数，它会在每次注册时都被调用。</p><p>解析后、命令前和命令后钩子方法共享一些影响命令处理循环的通用方式。</p><p>如果钩子引发异常：</p><ul>
<li>不会再调用任何类型的钩子（命令终结钩子除外）</li>
<li>如果命令尚未执行，它将不会被执行</li>
<li>异常消息将显示给用户</li>
</ul><p>特定类型的钩子方法有额外的选项，如下所述。</p></section><section><h2>解析后钩子<a href="#解析后钩子"><span>#</span></a></h2><p>解析后钩子在用户输入被解析后但在命令执行之前调用。这些钩子可用于：</p><ul>
<li>修改用户输入</li>
<li>在每个命令执行前运行代码</li>
<li>取消当前命令的执行</li>
<li>退出应用</li>
</ul><p>当解析后钩子被调用时，输出尚未被重定向，命令执行的计时器也尚未启动。</p><p>要定义和注册解析后钩子，请按以下方式操作：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_postparsing_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>params</span></span><span>:</span><span> cmd2.plugin.PostparsingData</span><span>) -&gt; cmd2.plugin.PostparsingData:</span></div></div><div><div><div>7</div></div><div><span>        </span><span># the statement object created from the user input</span></div></div><div><div><div>8</div></div><div><span>        </span><span># is available as params.statement</span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> params</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>cmd2.Cmd.register_postparsing_hook</code> 会检查传入可调用对象的方法签名，如果参数数量错误会引发 <code>TypeError</code>。如果传入的参数和返回值没有标注为 <code>PostparsingData</code>，也会引发 <code>TypeError</code>。</p><p>钩子方法会接收一个参数，一个 <code>cmd2.plugin.PostparsingData</code> 对象，我们称之为 <code>params</code>。<code>params</code> 包含两个属性。<code>params.statement</code> 是一个 <code>cmd2.Statement</code> 对象，描述了解析后的用户输入。<code>cmd2.Statement</code> 对象中有许多有用的属性，包括 <code>.raw</code>（包含用户输入的完整内容）。<code>params.stop</code> 默认设为 <code>False</code>。</p><p>钩子方法必须返回一个 <code>cmd2.plugin.PostparsingData</code> 对象，最方便的方式是直接返回传入钩子方法的对象。钩子方法可以修改对象的属性来影响应用的行为。如果 <code>params.stop</code> 被设为 <code>True</code>，则在命令执行之前会触发致命失败，应用将退出。</p><p>要修改用户输入，你需要创建一个新的 <code>cmd2.Statement</code> 对象并将其赋值给 <code>params.statement</code>。不要尝试直接修改 <code>cmd2.Statement</code> 对象的内容，那里有陷阱。相反，使用 <code>cmd2.Statement</code> 对象中的各种属性来构造新字符串，然后解析该字符串以创建新的 <code>cmd2.Statement</code> 对象。</p><p><code>cmd2.Cmd</code> 使用 <code>cmd2.parsing.StatementParser</code> 的实例来解析用户输入。该实例已配置了正确的命令终止符、多行命令和其他解析相关设置。该实例可通过 <code>cmd2.Cmd.statement_parser</code> 属性访问。以下是一个展示正确技术的简单示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>params</span></span><span>:</span><span> cmd2.plugin.PostparsingData</span><span>) -&gt; cmd2.plugin.PostparsingData:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> </span><span>"|"</span><span> </span><span>in</span><span> params.statement.raw:</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>newinput </span><span>=</span><span> params.statement.raw </span><span>+</span><span> </span><span>" | less"</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>params.statement </span><span>=</span><span> </span><span>self</span><span><span>.statement_parser.</span><span>parse</span><span>(newinput)</span></span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> params</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果解析后钩子返回的 <code>cmd2.plugin.PostparsingData</code> 对象的 <code>stop</code> 属性被设为 <code>True</code>：</p><ul>
<li>不会再调用任何类型的钩子（<a href="#%E5%91%BD%E4%BB%A4%E7%BB%88%E7%BB%93%E9%92%A9%E5%AD%90">命令终结钩子</a>除外）</li>
<li>命令将不会被执行</li>
<li>不会向用户显示错误消息</li>
<li>应用将退出</li>
</ul></section><section><h2>命令前钩子<a href="#命令前钩子"><span>#</span></a></h2><p>命令前钩子可以修改用户输入，但不能请求应用终止。如果你的钩子需要能够退出应用，应将其实现为解析后钩子。</p><p>一旦输出被重定向且计时器启动，所有通过 <code>cmd2.Cmd.register_precmd_hook</code> 注册的钩子都会被调用。操作方式如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_precmd_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>data</span></span><span>:</span><span> cmd2.plugin.PrecommandData</span><span>) -&gt; cmd2.plugin.PrecommandData:</span></div></div><div><div><div>7</div></div><div><span>        </span><span># the statement object created from the user input</span></div></div><div><div><div>8</div></div><div><span>        </span><span># is available as data.statement</span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>cmd2.Cmd.register_precmd_hook</code> 会检查传入可调用对象的方法签名，如果参数数量错误会引发 <code>TypeError</code>。如果参数和返回值没有标注为 <code>PrecommandData</code>，也会引发 <code>TypeError</code>。</p><p>你可以选择通过创建具有不同属性的新 <code>cmd2.Statement</code> 来修改用户输入（见上文）。如果这样做，请将新的 <code>cmd2.Statement</code> 对象赋值给 <code>data.statement</code>。</p><p>命令前钩子必须返回一个 <code>cmd2.plugin.PrecommandData</code> 对象。你不需要从头创建此对象，可以直接返回传入钩子的那个。</p><p>在所有已注册的命令前钩子被调用之后，<code>cmd2.Cmd.precmd</code> 会被调用。为了保持与 <code>cmd.Cmd</code> 的完全向后兼容性，此方法接收的是 <code>cmd2.Statement</code>，而非 <code>cmd2.plugin.PrecommandData</code> 对象。</p></section><section><h2>命令后钩子<a href="#命令后钩子"><span>#</span></a></h2><p>一旦命令方法返回（即 <code>do_command(self, statement)</code> 方法已被调用并返回），所有命令后钩子都会被调用。如果用户重定向了输出，此时输出仍然被重定向，命令计时器也仍在运行。</p><p>定义和注册命令后钩子的方式如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_postcmd_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>data</span></span><span>:</span><span> cmd2.plugin.PostcommandData</span><span>) -&gt; cmd2.plugin.PostcommandData:</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你的钩子会接收一个 <code>cmd2.plugin.PostcommandData</code> 对象，它有一个 <code>statement</code> 属性，描述了已执行的命令。如果你的命令后钩子方法被调用，你可以确定命令方法已被调用，且它没有引发异常。</p><p>如果任何命令后钩子引发异常，异常将显示给用户，不会再调用更多的命令后钩子方法。命令终结钩子（如果有）仍会被调用。</p><p>在所有已注册的命令后钩子被调用之后，<code>self.postcmd</code> 会被调用以保持与 <code>cmd.Cmd</code> 的完全向后兼容性。</p><p>如果任何命令后钩子（已注册的或 <code>self.postcmd</code>）返回的 <code>cmd2.plugin.PostcommandData</code> 对象的 stop 属性被设为 <code>True</code>，后续的命令后钩子仍会被调用，命令终结钩子也会被调用，但一旦所有这些钩子都被调用完毕，应用将终止。同样，如果 <code>self.postcmd</code> 返回 <code>True</code>，命令终结钩子会在应用终止之前被调用。</p><p>任何命令后钩子都可以在返回前更改 <code>stop</code> 属性的值，修改后的值将传递给下一个命令后钩子。最后一个命令后钩子返回的值将传递给命令终结钩子，后者可以进一步修改该值。如果你的钩子盲目地返回 <code>False</code>，之前钩子请求退出应用的请求将不会被满足。除非有充分理由，否则最好返回传入的值。</p><p>要故意且静默地跳过命令后钩子，命令可以引发以下任一异常：</p><ul>
<li><code>cmd2.exceptions.SkipPostcommandHooks</code></li>
<li><code>cmd2.exceptions.Cmd2ArgparseError</code></li>
</ul></section><section><h2>命令终结钩子<a href="#命令终结钩子"><span>#</span></a></h2><p>命令终结钩子即使在其他类型的钩子或命令方法引发异常时也会被调用。创建和注册命令终结钩子的方式如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_cmdfinalization_hook</span><span>(</span></span><span>self</span><span>.myhookmethod)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>myhookmethod</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>data</span></span><span>:</span><span> cmd2.plugin.CommandFinalizationData</span><span>) -&gt; cmd2.plugin.CommandFinalizationData:</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>命令终结钩子必须检查传入的 <code>cmd2.plugin.CommandFinalizationData</code> 对象的 <code>statement</code> 属性是否包含值。在某些情况下，这些钩子可能在用户输入被解析之前就被调用，因此你不能总是依赖于有 <code>statement</code> 属性。</p><p>如果之前的解析后或命令前钩子已请求应用终止，传递给第一个命令终结钩子的 <code>stop</code> 属性值将为 <code>True</code>。任何命令终结钩子都可以在返回前更改 <code>stop</code> 属性的值，修改后的值将传递给下一个命令终结钩子。最后一个命令终结钩子返回的值将决定应用是否终止。</p><p>这种命令终结钩子的方式很强大，但也可能引起问题。如果你的钩子盲目地返回 <code>False</code>，之前钩子请求退出应用的请求将不会被满足。除非有充分理由，否则最好返回传入的值。</p><p>如果任何命令终结钩子引发异常，不会再调用更多的命令终结钩子。如果最后一个返回值的钩子返回了 <code>True</code>，则异常将被渲染，应用将终止。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/misc_packaging/</id>
      <title type="text">Python cmd2：杂项功能与应用打包分发</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/misc_packaging/"/>
      <summary type="text">介绍 cmd2 的杂项功能（计时器、退出方式、select 选择器、命令禁用）以及应用打包分发的各种方式。</summary>
      <content type="html"><![CDATA[<section><h1>杂项功能<a href="#杂项功能"><span>#</span></a></h1><section><h2>计时器<a href="#计时器"><span>#</span></a></h2><p>开启计时器设置后，<code>cmd2</code> 将显示每个命令执行所花费的墙钟时间。</p></section><section><h2>退出方式<a href="#退出方式"><span>#</span></a></h2><p>与许多 shell 应用类似，<code>cmd2</code> 应用可以通过在空行上按 <code>Ctrl-D</code> 或执行 <code>quit</code> 命令来退出。</p></section><section><h2>select<a href="#select"><span>#</span></a></h2><p>向用户显示编号选项，类似于 bash 的 <code>select</code>。</p><p><code>app.select</code> 是在方法内部调用的（不是由用户直接调用；它是 <code>app.select</code>，而不是 <code>app.do_select</code>）。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>do_eat</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>sauce </span><span>=</span><span> </span><span>self</span><span><span>.</span><span>select</span><span>(</span></span><span>"sweet salty"</span><span>, </span><span>"Sauce? "</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> </span><span>"</span><span>{food}</span><span> with </span><span>{sauce}</span><span> sauce, yum!"</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> result.</span><span>format</span><span>(</span><span>food</span><span><span>=</span><span>arg, </span></span><span>sauce</span><span><span>=</span><span>sauce)</span></span></div></div><div><div><div>5</div></div><div><span>    </span><span>self</span><span><span>.stdout.</span><span>write</span><span>(result </span><span>+</span><span> </span></span><span>"</span><span>\n</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) eat wheaties</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>1. sweet</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>2. salty</span></div></div><div><div><div>4</div></div><div><span>Sauce? 2</span></div></div><div><div><div>5</div></div><div><span>wheaties with salty sauce, yum!</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/read_input.py" target="_blank">read_input.py</a> 文件中的 <code>do_eat</code> 方法了解如何使用 <code>select</code> 的示例。</p></section><section><h2>命令禁用<a href="#命令禁用"><span>#</span></a></h2><p><code>cmd2</code> 支持在运行时禁用命令。这在某些命令仅在应用处于特定状态时才应可用的情况下非常有用。当命令被禁用时，它不会出现在帮助菜单中或被 Tab 补全。如果用户尝试运行该命令，将打印开发者提供的特定消息。以下函数支持此功能：</p><ul>
<li><code>enable_command</code>：启用单个命令</li>
<li><code>enable_category</code>：启用整个分类的命令</li>
<li><code>disable_command</code>：禁用单个命令并设置在禁用期间运行该命令或请求帮助时打印的消息</li>
<li><code>disable_category</code>：禁用整个分类的命令并设置在禁用期间运行该分类中的任何命令或请求帮助时打印的消息</li>
</ul><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/help_categories.py" target="_blank">help_categories.py</a> 示例中的 <code>do_enable_commands()</code> 和 <code>do_disable_commands()</code> 函数了解演示。</p></section><section><h2>表格创建<a href="#表格创建"><span>#</span></a></h2><p>从 3.0.0 版本开始，<code>cmd2</code> 不再包含自定义的表格创建代码。</p><p>这是因为 <code>cmd2</code> 现在依赖于 <a href="https://github.com/Textualize/rich" target="_blank">rich</a>，它对这一功能有出色的支持。</p><p>请参阅 rich 的<a href="https://rich.readthedocs.io/en/latest/tables.html" target="_blank">表格文档</a>了解更多信息。</p><p><a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_tables.py" target="_blank">rich_tables.py</a> 示例演示了如何在 <code>cmd2</code> 应用中使用 <code>rich</code> 表格。</p></section><section><h2>主题定制<a href="#主题定制"><span>#</span></a></h2><p><code>cmd2</code> 提供了使用 <code>cmd2.theme.update_theme</code> 函数为应用配置整体主题的能力。这基于 <a href="https://rich.readthedocs.io/en/stable/reference/theme.html" target="_blank">rich.theme</a> 样式信息容器。你可以使用它来为应用打造品牌，并设置对用户群体有吸引力的整体一致外观。</p></section><section><h2>自定义补全菜单颜色<a href="#自定义补全菜单颜色"><span>#</span></a></h2><p><code>cmd2</code> 利用 <code>prompt-toolkit</code> 实现其 Tab 补全菜单。你可以通过在 <code>cmd2</code> 主题中重写以下样式来自定义补全菜单的颜色：</p><ul>
<li><code>Cmd2Style.COMPLETION_MENU</code> - 整个补全菜单容器的基础样式（设置背景）</li>
<li><code>Cmd2Style.COMPLETION_MENU_COMPLETION</code> - 单个未选中补全项的样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_CURRENT</code> - 当前选中补全项的样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_META</code> - 补全项旁边显示的”元信息”样式</li>
<li><code>Cmd2Style.COMPLETION_MENU_META_CURRENT</code> - 当前项的元信息样式</li>
</ul><p>默认情况下，当前选中的补全项和元数据使用绿色背景上的黑色文本来提供对比度。所有其他项默认保持 <code>prompt-toolkit</code> 的默认值。然而，<code>cmd2</code> 应用作者可以自由自定义这些样式，以匹配所需的视觉风格和/或品牌。</p></section><section><h2>示例<a href="#示例"><span>#</span></a></h2><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/rich_theme.py" target="_blank">rich_theme.py</a> 了解为 <code>cmd2</code> 应用配置自定义主题的简单示例。</p></section></section>
<section><h1>应用打包分发<a href="#应用打包分发"><span>#</span></a></h1><p>作为构建交互式命令行应用的通用工具，<code>cmd2</code> 被设计为可以多种方式使用。如何将 <code>cmd2</code> 应用分发给客户或最终用户取决于你。参阅 Python 打包权威机构的 <a href="https://packaging.python.org/overview/" target="_blank">Python 打包概述</a>了解 Python 生态系统中各种选项的详细讨论。</p><section><h2>发布到 Python 包索引（PyPI）<a href="#发布到-python-包索引pypi"><span>#</span></a></h2><p>最简单的方式是按照<a href="https://packaging.python.org/en/latest/tutorials/packaging-projects/" target="_blank">打包 Python 项目</a>教程操作。这将展示如何将应用打包为 Python 包并上传到 Python 包索引（<a href="https://pypi.org/" target="_blank">PyPI</a>）。发布后，用户将能够使用标准 Python 打包工具（如 <a href="https://pip.pypa.io/" target="_blank">pip</a> 或 <a href="https://github.com/astral-sh/uv" target="_blank">uv</a>）来安装它。</p><p>对此过程的小幅调整可以让你发布到私有 PyPI 镜像，如托管在 <a href="https://aws.amazon.com/codeartifact/" target="_blank">AWS CodeArtifact</a> 上的镜像或私有 <a href="https://jfrog.com/artifactory/" target="_blank">Artifactory</a> 服务器。</p></section><section><h2>使用 Docker 在容器中打包应用<a href="#使用-docker-在容器中打包应用"><span>#</span></a></h2><p>将 Python 应用打包在 <a href="https://www.docker.com/" target="_blank">Docker</a> 容器中在跨平台移植性和便利性方面非常出色，因为该容器将包含应用的所有依赖项，并在不会与操作系统依赖项冲突的隔离环境中运行它们。</p><p>这篇方便的博客文章将展示如何 <a href="https://www.docker.com/blog/how-to-dockerize-your-python-applications/" target="_blank">Docker 化你的 Python 应用</a>。</p></section><section><h2>将应用与 Python 一起打包为安装程序<a href="#将应用与-python-一起打包为安装程序"><span>#</span></a></h2><p>对于希望将 <code>cmd2</code> 应用打包为单个二进制镜像或压缩文件的开发者，我们可以根据个人和专业经验推荐以下工具：</p><ul>
<li><a href="https://github.com/Nuitka/Nuitka" target="_blank">Nuitka</a>
<ul>
<li>Nuitka 是一个用 Python 编写的 Python 编译器</li>
<li>你将 Python 应用提供给它，它会做很多聪明的事情，然后输出一个可执行文件或扩展模块</li>
<li>如果你有希望通过混淆应用背后的 Python 源代码来保护的知识产权，这特别方便</li>
</ul>
</li>
<li><a href="https://www.pyinstaller.org" target="_blank">PyInstaller</a>
<ul>
<li>将 Python 程序冻结（打包）为独立可执行文件</li>
<li>PyInstaller 将 Python 应用及其所有依赖项打包为单个包</li>
<li>用户可以运行打包的应用而无需安装 Python 解释器或任何模块</li>
</ul>
</li>
<li><a href="https://github.com/indygreg/PyOxidizer" target="_blank">PyOxidizer</a>
<ul>
<li>用 Rust 实现的现代 Python 应用打包和分发工具</li>
<li>用于生成嵌入 Python 和所有依赖项的二进制文件的实用工具</li>
<li>你可以将单个可执行文件复制到另一台机器上并运行其中包含的 Python 应用，开箱即用</li>
</ul>
</li>
</ul></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/modular_commands/</id>
      <title type="text">Python cmd2：模块化命令详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/modular_commands/"/>
      <summary type="text">详细介绍 cmd2 的模块化命令系统，包括 CommandSet 的定义、自动加载、动态加载/卸载、事件处理器和子命令注入。</summary>
      <content type="html"><![CDATA[<section><h1>模块化命令<a href="#模块化命令"><span>#</span></a></h1><section><h2>概述<a href="#概述"><span>#</span></a></h2><p>Cmd2 还允许开发者将其命令定义模块化为 <code>CommandSet</code> 对象。CommandSet 代表 <code>cmd2</code> 应用中命令的逻辑分组。默认情况下，<code>CommandSet</code> 对象需要手动注册。然而，通过在实例化 <code>cmd2.Cmd</code> 类时设置 <code>auto_load_commands=True</code>，可以自动发现和加载所有 <code>CommandSet</code> 对象。这还允许开发者动态地从 <code>cmd2</code> 应用中添加/移除命令。这对于添加额外功能的可加载插件非常有用。此外，它允许面向对象的封装和特定于 CommandSet 的状态的垃圾回收。</p><section><h3>功能特性<a href="#功能特性"><span>#</span></a></h3><ul>
<li><strong>模块化命令集</strong> - 命令可以被拆分为单独的模块，而不是在一个包含所有命令的”上帝类”中</li>
<li><strong>自动命令发现</strong> - 在应用中，只需定义和导入 CommandSet 就足以让 <code>cmd2</code> 发现和加载你的命令（如果设置了 <code>auto_load_commands=True</code>）。无需手动注册</li>
<li><strong>动态可加载/可卸载命令</strong> - 命令函数和 CommandSet 都可以在应用执行期间动态加载和卸载。这可以实现动态加载模块以添加额外命令等功能</li>
<li><strong>事件处理器</strong> - <code>CommandSet</code> 类中提供了四个事件处理器用于自定义初始化和清理步骤。详见<a href="#%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E5%99%A8">事件处理器</a></li>
<li><strong>子命令注入</strong> - 子命令可以独立于基础命令定义。这允许以操作为中心（而非以对象为中心）的命令系统，同时仍然围绕被管理的对象组织代码和处理器</li>
</ul><p>参阅 [cmd2.CommandSet][] 的 API 文档。</p><p>参阅<a href="https://github.com/python-cmd2/cmd2/tree/main/examples/modular_commands" target="_blank">示例</a>了解更多细节。</p></section></section><section><h2>定义命令<a href="#定义命令"><span>#</span></a></h2><section><h3>命令集<a href="#命令集"><span>#</span></a></h3><p>CommandSet 将多个命令组合在一起。插件将使用与在 <code>cmd2.Cmd</code> 中定义时相同的规则来检查 <code>CommandSet</code> 中的函数。命令必须以 <code>do_</code> 为前缀，帮助函数以 <code>help_</code> 为前缀，补全函数以 <code>complete_</code> 为前缀。</p><p>CommandSet 命令方法期望的参数与在 <code>cmd2.Cmd</code> 子类中定义时相同，只是 <code>self</code> 现在将引用 <code>CommandSet</code> 而不是 cmd2 实例。cmd2 实例可以通过在 <code>CommandSet</code> 注册时填充的 <code>self._cmd</code> 来访问。</p><p>只有在初始化器不接受参数时，CommandSet 才会被自动加载。如果需要提供初始化器参数，请参阅<a href="#%E6%89%8B%E5%8A%A8-commandset-%E6%9E%84%E9%80%A0">手动 CommandSet 构造</a>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> CommandSet</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>ExampleApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>CommandSets are automatically loaded. Nothing needs to be done.</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, </span><span>auto_load_commands</span><span>=</span><span>True</span><span>, **kwargs)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>def</span><span> </span><span>do_something</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>):</span></div></div><div><div><div>14</div></div><div><span>        </span><span>"""Something Command."""</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"this is the something command"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>class</span><span><span> </span><span>AutoLoadCommandSet</span><span>(CommandSet[ExampleApp])</span></span><span>:</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"My Category"</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>22</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>def</span><span> </span><span>do_hello</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>25</div></div><div><span>        </span><span>"""Hello Command."""</span></div></div><div><div><div>26</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Hello"</span><span>)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>def</span><span> </span><span>do_world</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>29</div></div><div><span>        </span><span>"""World Command."""</span></div></div><div><div><div>30</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"World"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>手动 CommandSet 构造<a href="#手动-commandset-构造"><span>#</span></a></h3><p>如果 CommandSet 类需要向初始化器提供参数，你可以手动构造 CommandSet 并将其传递给 Cmd2。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> CommandSet</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>ExampleApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>CommandSets with initializer parameters are provided in the initializer</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, </span><span>auto_load_commands</span><span>=</span><span>True</span><span>, **kwargs)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span>def</span><span> </span><span>do_something</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg</span></span><span>):</span></div></div><div><div><div>14</div></div><div><span>        </span><span>"""Something Command."""</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.last_result </span><span>=</span><span> </span></span><span>5</span></div></div><div><div><div>16</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"this is the something command"</span><span>)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>class</span><span><span> </span><span>CustomInitCommandSet</span><span>(CommandSet[ExampleApp])</span></span><span>:</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"My Category"</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>arg1</span></span><span>,</span><span><span> </span><span>arg2</span></span><span>):</span></div></div><div><div><div>23</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>24</div></div><div><span>        </span><span>self</span><span><span>._arg1 </span><span>=</span><span> arg1</span></span></div></div><div><div><div>25</div></div><div><span>        </span><span>self</span><span><span>._arg2 </span><span>=</span><span> arg2</span></span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>def</span><span> </span><span>do_show_arg1</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>28</div></div><div><span>        </span><span>"""Show Arg 1."""</span></div></div><div><div><div>29</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Arg1: </span><span>{</span><span>self</span><span>._arg1</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>def</span><span> </span><span>do_show_arg2</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>32</div></div><div><span>        </span><span>"""Show Arg 2."""</span></div></div><div><div><div>33</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Arg2: </span><span>{</span><span>self</span><span>._arg2</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>def</span><span> </span><span>main</span><span>():</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>my_commands </span><span>=</span><span> </span><span>CustomInitCommandSet</span><span>(</span><span>1</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>ExampleApp</span><span>(</span><span>command_sets</span><span><span>=</span><span>[my_commands])</span></span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>app.</span><span>cmdloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>类型提示与 self._cmd<a href="#类型提示与-self_cmd"><span>#</span></a></h3><p>当 <code>CommandSet</code> 被注册时，其 <code>_cmd</code> 属性会被填充为对 <code>cmd2.Cmd</code> 实例的引用。<code>CommandSet</code> 是一个<a href="https://docs.python.org/3/library/typing.html#typing.Generic" target="_blank">泛型</a>类，允许你指定它期望被加载到的特定 <code>cmd2.Cmd</code> 子类。</p><p>通过使用你的应用类参数化继承，你的 IDE 和静态分析工具（如 Mypy）将知道 <code>self._cmd</code> 的确切类型。这在访问主应用实例的自定义属性或方法时提供了完整的自动补全和类型验证。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> CommandSet</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>8</div></div><div><span>        </span><span>self</span><span><span>.custom_state </span><span>=</span><span> </span></span><span>"Some important data"</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>class</span><span><span> </span><span>MyCommands</span><span>(CommandSet[MyApp])</span></span><span>:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_check_state</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span># Type checkers know self._cmd is an instance of MyApp</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>f</span><span>"State: </span><span>{</span><span>self</span><span>._cmd.custom_state</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>动态命令<a href="#动态命令"><span>#</span></a></h3><p>你还可以通过在运行时安装和移除 CommandSet 来动态加载和卸载命令。例如，你可以支持运行时可加载的插件或根据状态添加/移除命令。</p><p>如果需要在运行时动态加载命令，你可能需要禁用命令自动加载。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> argparse</span></div></div><div><div><div>2</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>3</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> CommandSet, with_argparser, with_category</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>LoadableFruits</span><span>(CommandSet[</span></span><span>"ExampleApp"</span><span>])</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Fruits"</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>10</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_apple</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span>"""Apple Command."""</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Apple"</span><span>)</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>    </span><span>def</span><span> </span><span>do_banana</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>17</div></div><div><span>        </span><span>"""Banana Command."""</span></div></div><div><div><div>18</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Banana"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>class</span><span><span> </span><span>LoadableVegetables</span><span>(CommandSet[</span></span><span>"ExampleApp"</span><span>])</span><span>:</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Vegetables"</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>25</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>    </span><span>def</span><span> </span><span>do_arugula</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>28</div></div><div><span>        </span><span>"""Arugula Command."""</span></div></div><div><div><div>29</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Arugula"</span><span>)</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>def</span><span> </span><span>do_bokchoy</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>32</div></div><div><span>        </span><span>"""Bok Choy Command."""</span></div></div><div><div><div>33</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Bok Choy"</span><span>)</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>class</span><span><span> </span><span>ExampleApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>37</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>CommandSets are loaded via the `load` and `unload` commands</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>42</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, </span><span>auto_load_commands</span><span>=</span><span>False</span><span>, **kwargs)</span></div></div><div><div><div>43</div></div><div><span>        </span><span>self</span><span><span>._fruits </span><span>=</span><span> </span><span>LoadableFruits</span><span>()</span></span></div></div><div><div><div>44</div></div><div><span>        </span><span>self</span><span><span>._vegetables </span><span>=</span><span> </span><span>LoadableVegetables</span><span>()</span></span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>load_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>47</div></div><div><span><span>    </span></span><span>load_parser.</span><span>add_argument</span><span>(</span><span>"cmds"</span><span>, </span><span>choices</span><span><span>=</span><span>[</span></span><span>"fruits"</span><span>, </span><span>"vegetables"</span><span>])</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>@with_argparser</span><span><span>(</span><span>load_parser</span><span>)</span></span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>@with_category</span><span>(</span><span>"Command Loading"</span><span>)</span></div></div><div><div><div>51</div></div><div><span>    </span><span>def</span><span> </span><span>do_load</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>52</div></div><div><span>        </span><span>"""Load Command."""</span></div></div><div><div><div>53</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"fruits"</span><span>:</span></div></div><div><div><div>54</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>55</div></div><div><span>                </span><span>self</span><span><span>.</span><span>register_command_set</span><span>(</span></span><span>self</span><span>._fruits)</span></div></div><div><div><div>56</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits loaded"</span><span>)</span></div></div><div><div><div>57</div></div><div><span>            </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>58</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits already loaded"</span><span>)</span></div></div><div><div><div>59</div></div><div>
</div></div><div><div><div>60</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"vegetables"</span><span>:</span></div></div><div><div><div>61</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>62</div></div><div><span>                </span><span>self</span><span><span>.</span><span>register_command_set</span><span>(</span></span><span>self</span><span>._vegetables)</span></div></div><div><div><div>63</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables loaded"</span><span>)</span></div></div><div><div><div>64</div></div><div><span>            </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>65</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables already loaded"</span><span>)</span></div></div><div><div><div>66</div></div><div>
</div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>@with_argparser</span><span><span>(</span><span>load_parser</span><span>)</span></span></div></div><div><div><div>68</div></div><div><span>    </span><span>def</span><span> </span><span>do_unload</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>69</div></div><div><span>        </span><span>"""Unload Command."""</span></div></div><div><div><div>70</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"fruits"</span><span>:</span></div></div><div><div><div>71</div></div><div><span>            </span><span>self</span><span><span>.</span><span>unregister_command_set</span><span>(</span></span><span>self</span><span>._fruits)</span></div></div><div><div><div>72</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits unloaded"</span><span>)</span></div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"vegetables"</span><span>:</span></div></div><div><div><div>75</div></div><div><span>            </span><span>self</span><span><span>.</span><span>unregister_command_set</span><span>(</span></span><span>self</span><span>._vegetables)</span></div></div><div><div><div>76</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables unloaded"</span><span>)</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div>
</div></div><div><div><div>79</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>ExampleApp</span><span>()</span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>app.</span><span>cmdloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>事件处理器<a href="#事件处理器"><span>#</span></a></h2><p>以下函数在 <code>CommandSet</code> 生命周期的不同阶段被调用：</p><ul>
<li><strong>on_register</strong> - 由 <code>cmd2.Cmd</code> 在注册 <code>CommandSet</code> 的第一步调用。此时此类中定义的命令尚未添加到 CLI 对象。子类可以重写此方法以执行任何需要访问 Cmd 对象的初始化（例如，基于 CLI 状态数据配置命令及其解析器）</li>
<li><strong>on_registered</strong> - 由 <code>cmd2.Cmd</code> 在 <code>CommandSet</code> 注册完成且所有命令已添加到 CLI 后调用。子类可以重写此方法以执行与新添加命令相关的自定义步骤（例如，将它们设为禁用状态）</li>
<li><strong>on_unregister</strong> - 由 <code>cmd2.Cmd</code> 在注销 <code>CommandSet</code> 的第一步调用。子类可以重写此方法以执行任何需要其命令在 CLI 中注册的清理步骤</li>
<li><strong>on_unregistered</strong> - 由 <code>cmd2.Cmd</code> 在 <code>CommandSet</code> 注销完成且所有命令已从 CLI 中移除后调用。子类可以重写此方法以执行剩余的清理步骤</li>
</ul></section><section><h2>注入子命令<a href="#注入子命令"><span>#</span></a></h2><section><h3>说明<a href="#说明"><span>#</span></a></h3><p>使用 <code>@with_argparser</code> 和 <code>@as_subcommand_to</code> 装饰器，可以轻松地为命令定义子命令。这往往会将你的接口推向以对象为中心的接口。例如，假设你有一个管理媒体收藏的工具，你想管理电影或节目。以对象为中心的方式会要求你有 <code>movies</code> 和 <code>shows</code> 等基础命令，每个都有 <code>add</code>、<code>edit</code>、<code>list</code>、<code>delete</code> 子命令。如果你想呈现以操作为中心的命令集，使 <code>add</code>、<code>edit</code>、<code>list</code> 和 <code>delete</code> 成为基础命令，你就必须围绕这些类似的操作组织代码，而不是围绕被管理的类似对象组织代码。</p><p>子命令注入允许你将子命令注入到基础命令中，以呈现对用户合理的接口，同时仍然以对开发者更有逻辑意义的结构组织代码。</p></section><section><h3>示例<a href="#示例"><span>#</span></a></h3><p>此示例是上面动态命令示例的变体。引入了一个 <code>cut</code> 命令作为基础命令，每个 CommandSet 向其添加一个子命令。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> argparse</span></div></div><div><div><div>2</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>3</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> CommandSet, with_argparser, with_category</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>LoadableFruits</span><span>(CommandSet[</span></span><span>"ExampleApp"</span><span>])</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Fruits"</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>10</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>do_apple</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span>"""Apple Command."""</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Apple"</span><span>)</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>banana_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>banana_parser.</span><span>add_argument</span><span>(</span><span>"direction"</span><span>, </span><span>choices</span><span><span>=</span><span>[</span></span><span>"discs"</span><span>, </span><span>"lengthwise"</span><span>])</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span><span>@cmd2</span><span>.</span><span>as_subcommand_to</span></span><span>(</span><span>"cut"</span><span><span>,</span><span> </span></span><span>"banana"</span><span><span>,</span><span> banana_parser</span><span>)</span></span></div></div><div><div><div>20</div></div><div><span>    </span><span>def</span><span> </span><span>cut_banana</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>21</div></div><div><span>        </span><span>"""Cut banana"""</span></div></div><div><div><div>22</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"cutting banana: "</span><span><span> </span><span>+</span><span> ns.direction)</span></span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>class</span><span><span> </span><span>LoadableVegetables</span><span>(CommandSet[</span></span><span>"ExampleApp"</span><span>])</span><span>:</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>DEFAULT_CATEGORY</span><span> </span><span>=</span><span> </span><span>"Vegetables"</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>29</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>def</span><span> </span><span>do_arugula</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> cmd2.Statement</span><span>):</span></div></div><div><div><div>32</div></div><div><span>        </span><span>"""Arugula Command."""</span></div></div><div><div><div>33</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Arugula"</span><span>)</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>bokchoy_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>bokchoy_parser.</span><span>add_argument</span><span>(</span><span>"style"</span><span>, </span><span>choices</span><span><span>=</span><span>[</span></span><span>"quartered"</span><span>, </span><span>"diced"</span><span>])</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span><span>    </span></span><span><span>@cmd2</span><span>.</span><span>as_subcommand_to</span></span><span>(</span><span>"cut"</span><span><span>,</span><span> </span></span><span>"bokchoy"</span><span><span>,</span><span> bokchoy_parser</span><span>)</span></span></div></div><div><div><div>39</div></div><div><span>    </span><span>def</span><span> </span><span>cut_bokchoy</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>40</div></div><div><span>        </span><span>"""Cut bok choy."""</span></div></div><div><div><div>41</div></div><div><span>        </span><span>self</span><span><span>._cmd.</span><span>poutput</span><span>(</span></span><span>"Bok Choy"</span><span>)</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span>class</span><span><span> </span><span>ExampleApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>45</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>CommandSets are loaded dynamically at runtime via other commands.</span></div></div><div><div><div>47</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>50</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, </span><span>auto_load_commands</span><span>=</span><span>False</span><span>, **kwargs)</span></div></div><div><div><div>51</div></div><div><span>        </span><span>self</span><span><span>._fruits </span><span>=</span><span> </span><span>LoadableFruits</span><span>()</span></span></div></div><div><div><div>52</div></div><div><span>        </span><span>self</span><span><span>._vegetables </span><span>=</span><span> </span><span>LoadableVegetables</span><span>()</span></span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>load_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>load_parser.</span><span>add_argument</span><span>(</span><span>"cmds"</span><span>, </span><span>choices</span><span><span>=</span><span>[</span></span><span>"fruits"</span><span>, </span><span>"vegetables"</span><span>])</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span><span>    </span></span><span>@with_argparser</span><span><span>(</span><span>load_parser</span><span>)</span></span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>@with_category</span><span>(</span><span>"Command Loading"</span><span>)</span></div></div><div><div><div>59</div></div><div><span>    </span><span>def</span><span> </span><span>do_load</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>60</div></div><div><span>        </span><span>"""Load Command."""</span></div></div><div><div><div>61</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"fruits"</span><span>:</span></div></div><div><div><div>62</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>63</div></div><div><span>                </span><span>self</span><span><span>.</span><span>register_command_set</span><span>(</span></span><span>self</span><span>._fruits)</span></div></div><div><div><div>64</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits loaded"</span><span>)</span></div></div><div><div><div>65</div></div><div><span>            </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>66</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits already loaded"</span><span>)</span></div></div><div><div><div>67</div></div><div>
</div></div><div><div><div>68</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"vegetables"</span><span>:</span></div></div><div><div><div>69</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>70</div></div><div><span>                </span><span>self</span><span><span>.</span><span>register_command_set</span><span>(</span></span><span>self</span><span>._vegetables)</span></div></div><div><div><div>71</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables loaded"</span><span>)</span></div></div><div><div><div>72</div></div><div><span>            </span><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>73</div></div><div><span>                </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables already loaded"</span><span>)</span></div></div><div><div><div>74</div></div><div>
</div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>@with_argparser</span><span><span>(</span><span>load_parser</span><span>)</span></span></div></div><div><div><div>76</div></div><div><span>    </span><span>def</span><span> </span><span>do_unload</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>77</div></div><div><span>        </span><span>"""Unload Command."""</span></div></div><div><div><div>78</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"fruits"</span><span>:</span></div></div><div><div><div>79</div></div><div><span>            </span><span>self</span><span><span>.</span><span>unregister_command_set</span><span>(</span></span><span>self</span><span>._fruits)</span></div></div><div><div><div>80</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Fruits unloaded"</span><span>)</span></div></div><div><div><div>81</div></div><div>
</div></div><div><div><div>82</div></div><div><span>        </span><span>if</span><span><span> ns.cmds </span><span>==</span><span> </span></span><span>"vegetables"</span><span>:</span></div></div><div><div><div>83</div></div><div><span>            </span><span>self</span><span><span>.</span><span>unregister_command_set</span><span>(</span></span><span>self</span><span>._vegetables)</span></div></div><div><div><div>84</div></div><div><span>            </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"Vegetables unloaded"</span><span>)</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span><span>    </span></span><span>cut_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>87</div></div><div><span><span>    </span></span><span>cut_parser.</span><span>add_subparsers</span><span>(</span><span>title</span><span>=</span><span>"item"</span><span>, </span><span>help</span><span>=</span><span>"item to cut"</span><span>, </span><span>metavar</span><span>=</span><span>"ITEM"</span><span>, </span><span>required</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>88</div></div><div>
</div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>@with_argparser</span><span><span>(</span><span>cut_parser</span><span>)</span></span></div></div><div><div><div>90</div></div><div><span>    </span><span>def</span><span> </span><span>do_cut</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>ns</span></span><span>:</span><span> argparse.Namespace</span><span>):</span></div></div><div><div><div>91</div></div><div><span>        </span><span>"""Cut Command."""</span></div></div><div><div><div>92</div></div><div><span>        </span><span># 调用所选择的子命令处理函数</span></div></div><div><div><div>93</div></div><div><span><span>        </span></span><span>ns.</span><span>cmd2_subcommand_func</span><span>(ns)</span></div></div><div><div><div>94</div></div><div>
</div></div><div><div><div>95</div></div><div>
</div></div><div><div><div>96</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>97</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>ExampleApp</span><span>()</span></div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>app.</span><span>cmdloop</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/os/</id>
      <title type="text">Python cmd2：操作系统集成</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/os/"/>
      <summary type="text">介绍 cmd2 与操作系统的集成，包括输出重定向、执行 OS 命令、编辑器、终端分页器、退出码和命令行参数传递。</summary>
      <content type="html"><![CDATA[<section><h1>操作系统集成<a href="#操作系统集成"><span>#</span></a></h1><section><h2>如何重定向输出<a href="#如何重定向输出"><span>#</span></a></h2><p>参阅<a href="../prompt_redirection/#%E8%BE%93%E5%87%BA%E9%87%8D%E5%AE%9A%E5%90%91%E5%92%8C%E7%AE%A1%E9%81%93">输出重定向和管道</a>。</p></section><section><h2>在 cmd2 中执行操作系统命令<a href="#在-cmd2-中执行操作系统命令"><span>#</span></a></h2><p><code>cmd2</code> 包含一个 <code>shell</code> 命令，它在操作系统 shell 中执行其参数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) shell ls -al</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你使用 <code>cmd2</code> 中定义的默认<a href="../shortcuts_aliases_macros/#%E5%BF%AB%E6%8D%B7%E6%96%B9%E5%BC%8F">快捷方式</a>，你将获得一个 <code>!</code> 快捷方式用于 <code>shell</code>，允许你输入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) !ls -al</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p><code>cmd2</code> 在运行 shell 命令的整个过程中提供用户友好的 Tab 补全——首先是 shell 命令名称本身，然后是参数部分中的文件路径。</p><p>然而，<code>cmd2</code> 应用实际上<strong>成为</strong>了 shell，所以如果你为特定的 shell（如 <code>bash</code>、<code>zsh</code>、<code>fish</code> 等）配置了_额外的_ shell 补全，这在 <code>cmd2</code> 中将不可用。</p></div></div></section><section><h2>编辑器<a href="#编辑器"><span>#</span></a></h2><p><code>cmd2</code> 包含内置的 <code>edit</code> 命令，它运行文本编辑器并可选择打开文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) edit foo.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用的编辑器由 <code>editor</code> 可设置参数决定，可以是文本编辑器（如 <strong>vim</strong>）或图形编辑器（如 <strong>VSCode</strong>）。设置方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>set editor &lt;program_name&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你设置了 <code>EDITOR</code> 环境变量，这将是 <code>editor</code> 的默认值。如果没有，<code>cmd2</code> 将尝试在你操作系统的常见编辑器列表中搜索。</p></section><section><h2>终端分页器<a href="#终端分页器"><span>#</span></a></h2><p>任何命令的输出都可以使用 <code>cmd2.Cmd.ppaged</code> 方法逐页显示。</p><p>或者，可以使用 <code>!</code> 快捷方式直接调用终端分页器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) !less foo.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>一旦你进入终端分页器，该程序就暂时控制了你的终端，<strong>而不是</strong> <code>cmd2</code>。通常你可以使用方向键或 <code>&lt;PageUp&gt;</code>/<code>&lt;PageDown&gt;</code> 键滚动，或输入 <code>q</code> 退出分页器并将控制权返回给 <code>cmd2</code> 应用。</p></div></div></section><section><h2>退出码<a href="#退出码"><span>#</span></a></h2><p><code>cmd2</code> 应用的 <code>self.exit_code</code> 属性控制 <code>cmdloop()</code> 完成时返回的退出码。你的工作是确保在应用退出时通过调用 <code>sys.exit(app.cmdloop())</code> 将此退出码发送给 shell。</p></section><section><h2>带参数调用<a href="#带参数调用"><span>#</span></a></h2><p>通常你会通过输入以下内容来调用 <code>cmd2</code> 程序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ python mycmd2program.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ mycmd2program.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这两种方法都会启动你的程序并进入 <code>cmd2</code> 命令循环，允许用户输入命令，然后由你的程序执行。</p><p>你可能希望在不提示用户输入任何内容的情况下在程序中执行命令。有几种方法可以完成此任务。其中一种方法是通过标准输入将命令及其参数管道传递给你的程序。你不需要对程序做任何事情就可以使用此技术。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ echo "speak -p some words" | uv run examples/cmd_as_argument.py</span></div></div><div><div><div>2</div></div><div><span>omesay ordsway</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用相同的方法，你可以创建一个包含要运行的命令的文本文件，文件中每行一个命令。假设你的文件名为 <code>somecmds.txt</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>c:\cmd2&gt; type somecmds.txt | uv run examples/cmd_as_argument.py</span></div></div><div><div><div>2</div></div><div><span>omesay ordsway</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>默认情况下，<code>cmd2</code> 程序还会查找从操作系统 shell 传递的参数作为命令，并在进入命令循环之前执行这些命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ uv run examples/cmd_as_argument.py help</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可能需要对从操作系统 shell 传递的命令行参数有更多控制。例如，你可能有一个命令本身接受参数，甚至选项字符串。假设你想从操作系统 shell 运行 <code>speak</code> 命令，但让它用 pig latin（儿童黑话）说出来——只需将命令及其参数用双引号或单引号括起来即可：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ uv run examples/cmd_as_argument.py "speak -p hello there"</span></div></div><div><div><div>2</div></div><div><span>ellohay heretay</span></div></div><div><div><div>3</div></div><div><span>(Cmd)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你想使用自定义 <code>argparse</code> 解析器来收集高层应用参数，同时仍希望在调用时将额外的未知参数作为命令传递，请参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/argparse_example.py" target="_blank">argparse_example.py</a> 示例。使用这种方法，你可以这样调用它：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ uv run examples/argparse_example.py -c blue help</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看该示例的源代码，特别是 <code>if __name__ == "__main__":</code> 代码块，了解实现技巧。</p><section><h3>从其他 CLI/CLU 工具自动化 cmd2 应用<a href="#从其他-cliclu-工具自动化-cmd2-应用"><span>#</span></a></h3><p>虽然 <code>cmd2</code> 旨在创建进入 Read-Evaluate-Print-Loop（REPL）的<strong>交互式</strong>命令行应用，但很多时候将 <code>cmd2</code> 应用作为一次性运行的命令行实用程序用于自动化和脚本目的非常有用。</p><p>这可以通过结合 <code>cmd2</code> 的以下功能轻松实现：</p><ol>
<li>带参数调用 <code>cmd2</code> 应用的能力</li>
<li>离开 <code>cmd2</code> 应用时设置退出码的能力</li>
<li>使用 <code>quit</code> 命令退出 <code>cmd2</code> 应用的能力</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$ uv run examples/cmd_as_argument.py "speak -p hello there" quit</span></div></div><div><div><div>2</div></div><div><span>ellohay heretay</span></div></div><div><div><div>3</div></div><div><span>$</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/prompt_redirection/</id>
      <title type="text">Python cmd2：提示符定制、输出重定向与管道</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/prompt_redirection/"/>
      <summary type="text">介绍 cmd2 的提示符定制功能，包括自定义提示符、续行提示符、异步反馈和底部工具栏。介绍 cmd2 的输出重定向和管道功能，包括重定向到文件、剪贴板、管道传递给 shell 命令，以及禁用和限制。</summary>
      <content type="html"><![CDATA[<section><h1>提示符<a href="#提示符"><span>#</span></a></h1><p><code>cmd2</code> 在请求用户输入之前会发出一个可配置的提示符。</p><section><h2>自定义提示符<a href="#自定义提示符"><span>#</span></a></h2><p>此提示符可以通过设置 <code>cmd2.Cmd.prompt</code> 实例属性来配置。它包含应作为用户输入提示符打印的字符串。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例了解静态设置提示符的简单用例。</p></section><section><h2>续行提示符<a href="#续行提示符"><span>#</span></a></h2><p>当用户输入<a href="../app_setup/#%E5%A4%9A%E8%A1%8C%E5%91%BD%E4%BB%A4">多行命令</a>时，它可能跨越多行输入。第一行输入的提示符由 <code>cmd2.Cmd.prompt</code> 实例属性指定。后续行输入的提示符由 <code>cmd2.Cmd.continuation_prompt</code> 属性定义。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例了解自定义续行提示符的演示。</p></section><section><h2>更新提示符<a href="#更新提示符"><span>#</span></a></h2><p>如果你希望在命令之间更新提示符，可以使用<a href="../hooks/#%E5%BA%94%E7%94%A8%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90">应用生命周期钩子</a>之一，例如<a href="../hooks/#%E5%91%BD%E4%BB%A4%E5%90%8E%E9%92%A9%E5%AD%90">命令后钩子</a>。参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/python_scripting.py" target="_blank">python_scripting.py</a> 了解动态更新提示符的示例。</p></section><section><h2>异步反馈<a href="#异步反馈"><span>#</span></a></h2><p><code>cmd2</code> 提供了一个函数来向用户提供异步反馈，而不会干扰命令行。这允许在用户仍在提示符处输入文本时提供反馈。</p><ul>
<li><code>cmd2.Cmd.add_alert</code></li>
</ul><section><h3>异步反馈机制<a href="#异步反馈机制"><span>#</span></a></h3><p>警报可以通过两种方式与 CLI 交互：</p><ol>
<li><strong>消息打印</strong>：可以在当前提示行上方直接打印消息</li>
<li><strong>提示符更新</strong>：可以动态替换活动提示符的文本以反映变化的状态</li>
</ol><div><div><div></div><div>Note</div></div><div><p>为确保用户界面保持准确，如果警报是在当前提示符渲染之前创建的，则提示符更新将被忽略。这可以防止旧警报覆盖较新的提示符，尽管警报的消息仍会被打印。</p></div></div></section><section><h3>终端窗口管理<a href="#终端窗口管理"><span>#</span></a></h3><p><code>cmd2</code> 还提供了一个函数来更改终端窗口的标题。</p><ul>
<li><code>cmd2.Cmd.set_window_title</code></li>
</ul><p>理解这些函数的最简单方式是参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/async_printing.py" target="_blank">async_printing.py</a> 示例了解演示。</p></section></section><section><h2>底部工具栏<a href="#底部工具栏"><span>#</span></a></h2><p><code>cmd2</code> 支持一个可选的、持久的底部工具栏，在应用空闲等待输入时始终显示在终端窗口底部。</p><section><h3>启用工具栏<a href="#启用工具栏"><span>#</span></a></h3><p>要启用工具栏，在 <code>cmd2.Cmd.__init__</code> 构造函数中设置 <code>enable_bottom_toolbar=True</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span><span>enable_bottom_toolbar</span><span>=</span><span>True</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>自定义工具栏内容<a href="#自定义工具栏内容"><span>#</span></a></h3><p>你可以通过重写 <code>cmd2.Cmd.get_bottom_toolbar</code> 方法来自定义工具栏的内容。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> prompt_toolkit.formatted_text </span><span>import</span><span> AnyFormattedText</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>get_bottom_toolbar</span><span>(</span><span>self</span><span>) -&gt; AnyFormattedText:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>return</span><span> [</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>(</span><span>"ansigreen"</span><span>, </span><span>"My Application Name"</span><span>),</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>(</span><span>""</span><span>, </span><span>" - "</span><span>),</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>(</span><span>"ansiyellow"</span><span>, </span><span>"Current Status: Idle"</span><span>),</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>刷新工具栏<a href="#刷新工具栏"><span>#</span></a></h3><p>由于工具栏由 <code>prompt-toolkit</code> 作为提示符的一部分渲染，它会在提示符刷新时自然重绘。如果你希望工具栏自动更新（例如显示时钟），可以在 <code>cmd2.Cmd.__init__</code> 构造函数中将 <code>refresh_interval</code> 设置为大于 0.0 的值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span><span>refresh_interval</span><span>=</span><span>0.5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/getting_started.py" target="_blank">getting_started.py</a> 示例了解此技术的演示。</p></section></section></section>
<section><h1>输出重定向与管道<a href="#输出重定向与管道"><span>#</span></a></h1><p>与 POSIX shell 类似，命令的输出可以被重定向和/或管道传递。此功能完全跨平台，在 Windows、macOS 和 Linux 上的工作方式相同。</p><section><h2>输出重定向<a href="#输出重定向"><span>#</span></a></h2><section><h3>重定向到文件<a href="#重定向到文件"><span>#</span></a></h3><p>将 <code>cmd2</code> 命令的输出重定向到文件的方式与 POSIX shell 中相同：</p><ul>
<li>使用 <code>&gt;</code> 发送到文件，如 <code>mycommand args &gt; filename.txt</code></li>
<li>使用 <code>&gt;&gt;</code> 追加到文件，如 <code>mycommand args &gt;&gt; filename.txt</code></li>
</ul><p>如果你需要在命令中包含任何这些重定向字符，可以将它们用引号括起来：<code>mycommand 'with &gt; in the argument'</code>。</p></section><section><h3>重定向到剪贴板<a href="#重定向到剪贴板"><span>#</span></a></h3><p><code>cmd2</code> 输出重定向支持大多数 shell 中没有的额外功能——如果 <code>&gt;</code> 或 <code>&gt;&gt;</code> 后面的文件名留空，则输出被重定向到操作系统剪贴板，以便可以粘贴到另一个程序中。</p><ul>
<li>使用 <code>mycommand args &gt;</code> 覆盖剪贴板</li>
<li>使用 <code>mycommand args &gt;&gt;</code> 追加到剪贴板</li>
</ul></section></section><section><h2>管道<a href="#管道"><span>#</span></a></h2><p>将 <code>cmd2</code> 命令的输出管道传递给 shell 命令的方式与 POSIX shell 中相同：</p><ul>
<li>使用 <code>|</code> 作为 shell 命令的输入管道传递，如 <code>mycommand args | wc</code></li>
</ul></section><section><h2>多管道和重定向<a href="#多管道和重定向"><span>#</span></a></h2><p>支持多个管道，后面可选择跟随重定向。因此可以执行类似以下的操作：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) help | grep py | wc &gt; output.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上述运行 <strong>help</strong> 命令，将其输出管道传递给搜索包含 <em>py</em> 的行的 <strong>grep</strong>，然后将 grep 的输出管道传递给 <strong>wc</strong> “单词计数”命令，最后将该输出重定向到名为 <em>output.txt</em> 的文件。</p></section><section><h2>禁用重定向<a href="#禁用重定向"><span>#</span></a></h2><div><div><div></div><div>Note</div></div><div><p>如果你希望禁用 cmd2 的输出重定向和管道功能，可以通过将 <code>cmd2.Cmd</code> 类实例的 <code>allow_redirection</code> 属性设为 <code>False</code> 来实现。例如，如果你出于安全原因希望限制最终用户写入磁盘或与 shell 命令交互的能力时，这会很有用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> cmd2 </span><span>import</span><span> Cmd</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(</span><span>allow_redirection</span><span>=</span><span>False</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>cmd2 的解析器仍然会将 <code>&gt;</code>、<code>&gt;&gt;</code> 和 <code>|</code> 符号视为输出重定向和管道符号，并相应地从命令行参数中去除它们之后的参数。但命令的输出不会被重定向到文件或管道传递给 shell 命令。</p></div></div></section><section><h2>重定向的限制<a href="#重定向的限制"><span>#</span></a></h2><div><div><div></div><div>Warning</div></div><div><p><code>cmd2</code> 应用中的重定向和管道有一些限制：</p><ul>
<li>只能管道传递给 shell 命令，不能传递给其他 <code>cmd2</code> 应用命令</li>
<li><strong>stdout</strong> 会被重定向/管道传递，<strong>stderr</strong> 不会</li>
</ul></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/scripting/</id>
      <title type="text">Python cmd2：脚本功能详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/scripting/"/>
      <summary type="text">详细介绍 cmd2 的脚本功能，包括命令脚本和 Python 脚本的创建、运行，以及 cmd2 API 的开发原则和高级用法。</summary>
      <content type="html"><![CDATA[<section><h1>脚本<a href="#脚本"><span>#</span></a></h1><p>操作系统 shell 长期以来一直能够执行保存在文本文件中的命令序列。这些脚本文件简化了长命令序列的重复执行。<code>cmd2</code> 支持两种类似的机制：命令脚本和 Python 脚本。</p><section><h2>命令脚本<a href="#命令脚本"><span>#</span></a></h2><p>命令脚本包含在 <code>cmd2</code> 应用提示符处输入的命令序列。与操作系统 shell 脚本不同，命令脚本不能包含逻辑或循环。</p><section><h3>创建命令脚本<a href="#创建命令脚本"><span>#</span></a></h3><p>命令脚本可以通过以下几种方式创建：</p><ul>
<li>使用你选择的任何方法创建文本文件</li>
<li>使用内置的 <a href="../async_builtin_clipboard/#edit">edit</a> 命令创建或编辑现有文本文件</li>
<li>使用 <a href="../history/#%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97">history -s</a> 将之前输入的命令保存到脚本文件</li>
</ul><p>如果你从头创建文本文件，只需每行包含一个命令，就像你在 <code>cmd2</code> 应用中输入的那样。</p></section><section><h3>运行命令脚本<a href="#运行命令脚本"><span>#</span></a></h3><p>命令脚本文件可以使用内置的 <a href="../async_builtin_clipboard/#run_script">run_script</a> 命令或 <code>@</code> 快捷方式（如果你的应用使用默认快捷方式）执行。支持 ASCII 和 UTF-8 编码的 Unicode 文本文件。<a href="../async_builtin_clipboard/#run_script">run_script</a> 命令支持文件系统路径的 Tab 补全。还有一个变体命令 <code>\_relative_run_script</code> 或 <code>@@</code> 快捷方式（如果使用默认快捷方式），用于在脚本中使用相对于第一个脚本的路径。</p></section><section><h3>注释<a href="#注释"><span>#</span></a></h3><p>如果第一个非空白字符是 <code>#</code>，则命令行是注释。这意味着命令中后面出现的任何 <code>#</code> 字符都将被视为字面量。这同样适用于多行命令中间的 <code>#</code>，即使它是行的第一个字符。</p><p>注释在脚本中很有用，但在交互式会话中通常不使用。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) # this is a comment</span></div></div><div><div><div>2</div></div><div><span>(Cmd) command # this is not a comment</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>Python 脚本<a href="#python-脚本"><span>#</span></a></h2><p>如果你需要逻辑流、循环、分支或其他高级功能，可以编写一个在 <code>cmd2</code> 应用上下文中执行的 Python 脚本。此脚本使用 <a href="../async_builtin_clipboard/#run_pyscript">run_pyscript</a> 命令运行。以下是使用 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/scripts/arg_printer.py" target="_blank">arg_printer.py</a> pyscript 的简单示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) run_pyscript examples/scripts/arg_printer.py foo bar 'baz 23'</span></div></div><div><div><div>2</div></div><div><span>Running Python script 'arg_printer.py' which was called with 3 arguments</span></div></div><div><div><div>3</div></div><div><span>arg 1: 'foo'</span></div></div><div><div><div>4</div></div><div><span>arg 2: 'bar'</span></div></div><div><div><div>5</div></div><div><span>arg 3: 'baz 23'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="../async_builtin_clipboard/#run_pyscript">run_pyscript</a> 支持文件系统路径的 Tab 补全，并且如上所示，它具有向调用的脚本传递命令行参数的能力。</p></section><section><h2>开发 cmd2 API<a href="#开发-cmd2-api"><span>#</span></a></h2><p>如果你作为应用设计者没有显式禁用 <code>run_pyscript</code> 命令，你应该假设你的应用将被用于更高级别的 Python 脚本。以下部分作为指南，突出 API 功能的生产和消费中可能的陷阱。为清晰起见，“脚本编写者”编写 pyscripts，“设计者”是 <code>cmd2</code> 应用作者。</p><section><h3>基础<a href="#基础"><span>#</span></a></h3><p>无需设计者做任何工作，脚本编写者就可以利用简单的 <code>app</code> 调用来组合工作流。<code>run_pyscript</code> app 调用的结果产生一个 <code>CommandResult</code> 对象，暴露四个成员：<code>stdout</code>、<code>stderr</code>、<code>stop</code> 和 <code>data</code>。</p><p><code>stdout</code> 和 <code>stderr</code> 是相当直接的普通数据流表示，准确反映用户在正常 cmd2 交互期间看到的内容。<code>stop</code> 包含有关调用的命令如何结束其生命周期的信息。最后 <code>data</code> 包含设计者通过 <code>self.last_result</code>（如果从 CommandSet 内部调用则为 <code>self._cmd.last_result</code>）设置的任何信息。</p><p>使用 <a href="../async_builtin_clipboard/#run_pyscript">run_pyscript</a> 执行的 Python 脚本可以通过以下语法运行 <code>cmd2</code> 应用命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>app</span><span>(</span></span><span>'command args'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中：</p><ul>
<li><code>app</code> 是一个可配置的名称，可以通过设置 <code>cmd2.Cmd.py_bridge_name</code> 属性来更改</li>
<li><code>command</code> 和 <code>args</code> 的输入方式与应用用户的输入方式完全相同</li>
</ul><p>使用 f-字符串通常是最直接和易读的参数提供方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>first </span><span>=</span><span> </span></span><span>'first'</span></div></div><div><div><div>2</div></div><div><span><span>second </span><span>=</span><span> </span></span><span>'second'</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>app</span><span>(</span></span><span>f</span><span>'command </span><span>{</span><span>first</span><span>}</span><span> -t </span><span>{</span><span>second</span><span>}</span><span>'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>参阅 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/python_scripting.py" target="_blank">python_scripting.py</a> 示例和相关的 <a href="https://github.com/python-cmd2/cmd2/blob/main/examples/scripts/conditional.py" target="_blank">conditional.py</a> 脚本了解更多信息。</p></section><section><h3>设计原则<a href="#设计原则"><span>#</span></a></h3><p>如果你的 cmd2 应用遵循 <a href="https://en.wikipedia.org/wiki/Unix_philosophy" target="_blank">Unix 设计哲学</a>，脚本编写者将拥有最大的灵活性来使用不同命令创建工作流。如果设计者的应用更完整且不太可能在未来被扩展，脚本编写者可以使用简单的串行脚本，只需很少的控制流。无论哪种情况，设计者做出的选择都会对脚本编写者产生影响。</p><div><div><div></div><div>Note</div></div><div><p>作为设计者，你应该从内向外设计。你的代码在较低级别上进行单元测试要比在高级别上容易得多。虽然有 cmd2 的回归测试扩展，但单元测试在开发中总是更快。</p></div></div><div><div><div></div><div>Warning</div></div><div><p>高级 pyscript 了解甚至访问应用的低级类库是糟糕的设计。除非必要，否则请尽可能抵制这种冲动。</p></div></div></section><section><h3>开发基础 API<a href="#开发基础-api"><span>#</span></a></h3><p>默认情况下，<code>cmd2</code> 允许脚本编写者利用所有暴露的 <code>do_*</code> 命令。作为脚本编写者，你可以通过 <code>stdout</code> 和 <code>stderr</code> 轻松与应用交互。</p><p><code>cmd2</code> pyscripts 首先需要<strong>有效的</strong> Python 代码。</p><div><div><div></div><div>Warning</div></div><div><p>一个常见的误解是所有应用异常都会从下面传播上来。事实并非如此。<code>cmd2</code> 捕获所有应用异常，没有方法来处理它们。</p></div></div><p>当执行不带参数的 <code>speak</code> 命令时，你会看到以下错误：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) speak</span></div></div><div><div><div>2</div></div><div><span>Usage: speak [-h] [-p] [-s] [-r REPEAT] words [...]</span></div></div><div><div><div>3</div></div><div><span>Error: the following arguments are required: words</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即使这是一个完全合格的 <code>cmd2</code> 错误，pyscript 也必须检查此错误并执行错误检查：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>result </span><span>=</span><span> </span><span>app</span><span>(</span></span><span>"speak"</span><span>)</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>if</span><span> </span><span>not</span><span> result:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(result.stderr)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 Python 开发中，最好在用户输入后快速失败：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>result </span><span>=</span><span> </span><span>app</span><span>(</span></span><span>"speak TRUTH!!"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>if</span><span> </span><span>not</span><span> result:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>print</span><span>(</span><span>"Something went wrong"</span><span>)</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>()</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>"Continuing along.."</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过仅使用 <code>stdout</code> 和 <code>stderr</code>，可以使用基本控制流来链接命令。</p></section><section><h3>开发高级 API<a href="#开发高级-api"><span>#</span></a></h3><p>到目前为止，我们还没有关注脚本编写者的需求。如果在创建 pyscripts 时不必从 <code>stdout</code> 解析数据，那不是很好吗？我们可以通过在 <code>do_*</code> 命令末尾添加一小行来满足脚本编写者的需求。</p><p><code>self.last_result = &lt;value&gt;</code></p><p>添加上述行可以增强 cmd2 应用并开启一个新的可能性世界。</p><div><div><div></div><div>Tip</div></div><div><p>在 CommandSet 内部为命令函数设置结果时，请使用私有 cmd 实例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>self</span><span><span>._cmd.last_result </span><span>=</span><span> </span><span>&lt;</span><span>value</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></div></div><p><strong>设计者应返回简单的标量类型作为命令结果，而不是复杂对象。</strong> 如果提供类对象有益，设计者应选择不可变类型而非可变类型，并且永远不要提供对类成员的直接访问，因为这可能导致违反<a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle" target="_blank">开闭原则</a>。</p><p>在可能的情况下，frozen dataclass 是数据操作的理想轻量级解决方案。</p><p>以下应用有两个命令：<code>build</code> 和 <code>status</code>。假设构建操作发生在 REST API 端点的其他地方，并且有显著的计算成本。status 命令将只显示构建任务的当前状态。应用已经提供了用户启动构建和确定其状态所需的一切。然而，问题在于对于长时间运行的进程，用户可能希望等待其完成。设计者可能想要创建一个命令来启动构建然后轮询状态直到完成，但这种情况最好作为脚本来解决。</p><p>app.py:</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python</span></div></div><div><div><div>2</div></div><div><span>"""A simple cmd2 application."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>5</div></div><div><span>from</span><span> dataclasses </span><span>import</span><span> dataclass</span></div></div><div><div><div>6</div></div><div><span>from</span><span> random </span><span>import</span><span> choice, randint</span></div></div><div><div><div>7</div></div><div><span>from</span><span> typing </span><span>import</span><span> Optional</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>10</div></div><div><span>from</span><span> cmd2.parsing </span><span>import</span><span> Statement</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>@dataclass</span><span>(</span><span>frozen</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>14</div></div><div><span>class</span><span><span> </span><span>BuildStatus</span></span><span>:</span></div></div><div><div><div>15</div></div><div><span>    </span><span>id</span><span>: </span><span>int</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>name: </span><span>str</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>status: </span><span>str</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>class</span><span><span> </span><span>FirstApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>21</div></div><div><span>    </span><span>"""A simple cmd2 application."""</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>24</div></div><div><span>        </span><span>self</span><span><span>._status_cache </span><span>=</span><span> </span></span><span>dict</span><span>()</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>    </span><span>def</span><span> </span><span>_start_build</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>) -&gt; BuildStatus:</span></div></div><div><div><div>27</div></div><div><span>        </span><span>return</span><span><span> </span><span>BuildStatus</span><span>(</span><span>randint</span><span>(</span></span><span>10</span><span>, </span><span>100</span><span>), name, </span><span>"Started"</span><span>)</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span>def</span><span> </span><span>_get_status</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>) -&gt; Optional[BuildStatus]:</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>status </span><span>=</span><span> </span><span>self</span><span><span>._status_cache.</span><span>get</span><span>(name)</span></span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>status_types </span><span>=</span><span> [</span><span>"canceled"</span><span>, </span><span>"restarted"</span><span>, </span><span>"error"</span><span>, </span><span>"finished"</span><span>]</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>        </span><span>if</span><span><span> status.status </span><span>!=</span><span> </span></span><span>"finished"</span><span>:</span></div></div><div><div><div>34</div></div><div><span><span>            </span></span><span>status </span><span>=</span><span> </span><span>BuildStatus</span><span>(status.id, status.name, </span><span>choice</span><span>(status_types))</span></div></div><div><div><div>35</div></div><div><span>            </span><span>self</span><span><span>._status_cache[name] </span><span>=</span><span> status</span></span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>        </span><span>return</span><span> status</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>build_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>build_parser.</span><span>add_argument</span><span>(</span><span>"name"</span><span>, </span><span>help</span><span>=</span><span>"Name of build to start"</span><span>)</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span><span>    </span></span><span><span>@cmd2</span><span>.</span><span>with_argparser</span></span><span><span>(</span><span>build_parser</span><span>)</span></span></div></div><div><div><div>43</div></div><div><span>    </span><span>def</span><span> </span><span>do_build</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> Statement</span><span>):</span></div></div><div><div><div>44</div></div><div><span>        </span><span>"""Executes a long running process at an API endpoint"""</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>status </span><span>=</span><span> </span><span>self</span><span><span>.</span><span>_start_build</span><span>(args.name)</span></span></div></div><div><div><div>46</div></div><div><span>        </span><span>self</span><span><span>._status_cache[args.name] </span><span>=</span><span> status</span></span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Build </span><span>{</span><span><span>args.name.</span><span>upper</span><span>()</span></span><span>}</span><span> successfully started with id : </span><span>{</span><span>status.id</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>49</div></div><div><span>        </span><span>self</span><span><span>.last_result </span><span>=</span><span> status</span></span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>status_parser </span><span>=</span><span> cmd2.</span><span>Cmd2ArgumentParser</span><span>()</span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>status_parser.</span><span>add_argument</span><span>(</span><span>"name"</span><span>, </span><span>help</span><span>=</span><span>"Name of build determine status of"</span><span>)</span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span><span>    </span></span><span><span>@cmd2</span><span>.</span><span>with_argparser</span></span><span><span>(</span><span>status_parser</span><span>)</span></span></div></div><div><div><div>55</div></div><div><span>    </span><span>def</span><span> </span><span>do_status</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>args</span></span><span>:</span><span> Statement</span><span>):</span></div></div><div><div><div>56</div></div><div><span>        </span><span>"""Shows the current status of a build"""</span></div></div><div><div><div>57</div></div><div><span><span>        </span></span><span>status </span><span>=</span><span> </span><span>self</span><span><span>.</span><span>_get_status</span><span>(args.name)</span></span></div></div><div><div><div>58</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>f</span><span>"Status for Build: </span><span>{</span><span>args.name</span><span>}</span><span> </span><span>\n</span><span> </span><span>{</span><span>status.status</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>59</div></div><div><span>        </span><span>self</span><span><span>.last_result </span><span>=</span><span> status</span></span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>63</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>c </span><span>=</span><span> </span><span>FirstApp</span><span>()</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(c.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>以下是通过 pyscript 的可能解决方案：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>import</span><span> time</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># start build</span></div></div><div><div><div>5</div></div><div><span><span>result </span><span>=</span><span> </span><span>app</span><span>(</span></span><span>"build tower"</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># If there was an error then exit</span></div></div><div><div><div>8</div></div><div><span>if</span><span> </span><span>not</span><span> result:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>print</span><span>(</span><span>"Build failed"</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>()</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># This is a BuildStatus dataclass object</span></div></div><div><div><div>13</div></div><div><span><span>build </span><span>=</span><span> result.data</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>print</span><span>(</span><span>f</span><span>"Build </span><span>{</span><span>build.name</span><span>}</span><span> : </span><span>{</span><span>build.status</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># Poll status</span></div></div><div><div><div>18</div></div><div><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>19</div></div><div><span>    </span><span># Perform status check</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> </span><span>app</span><span>(</span><span>"status tower"</span><span>)</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>    </span><span># error checking</span></div></div><div><div><div>23</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> result:</span></div></div><div><div><div>24</div></div><div><span>        </span><span>print</span><span>(</span><span>"Unable to determine status"</span><span>)</span></div></div><div><div><div>25</div></div><div><span>        </span><span>break</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>build_status </span><span>=</span><span> result.data</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span># If the status shows complete then the script is done</span></div></div><div><div><div>30</div></div><div><span>    </span><span>if</span><span> build_status.status </span><span>in</span><span> [</span><span>"finished"</span><span>, </span><span>"canceled"</span><span>]:</span></div></div><div><div><div>31</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"Build </span><span>{</span><span>build.name</span><span>}</span><span> has completed"</span><span>)</span></div></div><div><div><div>32</div></div><div><span>        </span><span>break</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"Current Status: </span><span>{</span><span>build_status.status</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>time.</span><span>sleep</span><span>(</span><span>1</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/settings_plugins/</id>
      <title type="text">Python cmd2：设置系统与插件系统详解</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/settings_plugins/"/>
      <summary type="text">详细介绍 cmd2 的设置系统，包括内置设置（allow_style、debug、echo 等）、创建新设置和隐藏内置设置。同时，介绍插件系统，包括创建插件、加载插件和使用插件的功能。</summary>
      <content type="html"><![CDATA[<section><h1>设置<a href="#设置"><span>#</span></a></h1><p>设置为用户提供了控制基于 <code>cmd2</code> 应用行为的机制。设置存储在 <code>cmd2.Cmd</code> 子类的受保护实例属性中。开发者可以为这些设置设置默认值，用户可以在运行时使用 <a href="../async_builtin_clipboard/#set">set</a> 命令查看和修改它们。开发者可以<a href="#%E5%88%9B%E5%BB%BA%E6%96%B0%E8%AE%BE%E7%BD%AE">创建新设置</a>，也可以对用户<a href="#%E9%9A%90%E8%97%8F%E8%AE%BE%E7%BD%AE">隐藏内置设置</a>。</p><section><h2>内置设置<a href="#内置设置"><span>#</span></a></h2><p><code>cmd2</code> 有许多内置设置。这些设置控制某些应用功能和<a href="../async_builtin_clipboard/#%E5%86%85%E7%BD%AE%E5%91%BD%E4%BB%A4">内置命令</a>的行为。用户可以使用 <a href="../async_builtin_clipboard/#set">set</a> 命令显示所有设置并修改任何设置的值。</p><section><h3>allow_style<a href="#allow_style"><span>#</span></a></h3><p><code>cmd2</code> 程序生成的输出可能包含 ANSI 转义序列，指示终端对输出应用颜色或文本样式（即粗体）。<code>allow_style</code> 设置控制以下方法生成的输出中这些转义序列的行为：</p><ul>
<li><code>cmd2.Cmd.perror</code></li>
<li><code>cmd2.Cmd.pexcept</code></li>
<li><code>cmd2.Cmd.pfeedback</code></li>
<li><code>cmd2.Cmd.poutput</code></li>
<li><code>cmd2.Cmd.ppaged</code></li>
<li><code>cmd2.Cmd.print_to</code></li>
<li><code>cmd2.Cmd.psuccess</code></li>
<li><code>cmd2.Cmd.pwarning</code></li>
</ul><p>此设置可以是三个值之一：</p><ul>
<li><code>Never</code> - 从输出中去除所有指示终端设置输出样式的 ANSI 转义序列</li>
<li><code>Terminal</code> - （默认值）当输出发送到终端时传递 ANSI 转义序列，但如果输出被重定向到管道或文件，则去除转义序列</li>
<li><code>Always</code> - ANSI 转义序列始终传递到输出</li>
</ul></section><section><h3>debug<a href="#debug"><span>#</span></a></h3><p>此设置的默认值为 <code>False</code>，这导致 <code>cmd2.Cmd.pexcept</code> 方法只显示异常的消息。然而，如果 debug 设置为 <code>True</code>，则会打印整个堆栈跟踪。</p></section><section><h3>echo<a href="#echo"><span>#</span></a></h3><p>如果为 <code>True</code>，用户发出的每个命令在执行前会被回显到屏幕上。这在运行脚本时特别有用。在提示符处运行命令时不会出现此行为。</p></section><section><h3>editor<a href="#editor"><span>#</span></a></h3><p>类似于 <code>EDITOR</code> shell 变量，此设置包含 <a href="../async_builtin_clipboard/#edit">edit</a> 命令应运行的程序名称。</p></section><section><h3>max_completion_table_items<a href="#max_completion_table_items"><span>#</span></a></h3><p>补全表中显示的最大项目数。补全表是一种特殊的补全提示，显示正在补全的项目的详细信息。Tab 补全 <code>set</code> 命令可以查看示例。</p><p>如果补全建议的数量超过 <code>max_completion_table_items</code>，则不会显示表格。</p></section><section><h3>quiet<a href="#quiet"><span>#</span></a></h3><p>如果为 <code>True</code>，调用 <code>cmd2.Cmd.pfeedback</code> 生成的输出将被抑制。如果为 <code>False</code>，输出将发送到 <code>stdout</code>。</p></section><section><h3>scripts_add_to_history<a href="#scripts_add_to_history"><span>#</span></a></h3><p>如果为 <code>True</code>，脚本和 pyscripts 会将命令添加到历史记录。此设置的默认值为 <code>True</code>。</p></section><section><h3>timing<a href="#timing"><span>#</span></a></h3><p>如果为 <code>True</code>，将报告每个执行命令的经过时间。</p></section></section><section><h2>创建新设置<a href="#创建新设置"><span>#</span></a></h2><p>你的应用可以定义代码可以引用的用户可设置参数。在初始化代码中：</p><ol>
<li>创建一个带有默认值的实例属性</li>
<li>创建一个描述你的设置的 <a href="https://cmd2.readthedocs.io/en/latest/api/utils/#cmd2.utils.Settable" target="_blank">Settable</a> 对象</li>
<li>将 <code>Settable</code> 对象传递给 <code>add_settable</code> 方法</li>
</ol><p>下面是一个示例，展示了如何创建一个新设置：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env python</span></div></div><div><div><div>2</div></div><div><span>"""A sample application for cmd2 demonstrating customized environment parameters."""</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span><span> </span><span>EnvironmentApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>"""Example cmd2 application."""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>12</div></div><div><span>        </span><span>self</span><span><span>.degrees_c </span><span>=</span><span> </span></span><span>22</span></div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.sunny </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>.</span><span>add_settable</span><span>(</span></span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>cmd2.</span><span>Settable</span><span>(</span><span>"degrees_c"</span><span>, </span><span>int</span><span>, </span><span>"Temperature in Celsius"</span><span>, </span><span>self</span><span>, </span><span>onchange_cb</span><span>=</span><span>self</span><span>._onchange_degrees_c)</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>17</div></div><div><span>        </span><span>self</span><span><span>.</span><span>add_settable</span><span>(cmd2.</span><span>Settable</span><span>(</span></span><span>"sunny"</span><span>, </span><span>bool</span><span>, </span><span>"Is it sunny outside?"</span><span>, </span><span>self</span><span>))</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>def</span><span> </span><span>do_sunbathe</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_arg</span></span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>20</div></div><div><span>        </span><span>"""Attempt to sunbathe."""</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> </span><span>self</span><span><span>.degrees_c </span><span>&lt;</span><span> </span></span><span>20</span><span>:</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>result </span><span>=</span><span> </span><span>f</span><span>"It's </span><span>{</span><span>self</span><span>.degrees_c</span><span>}</span><span> C - are you a penguin?"</span></div></div><div><div><div>23</div></div><div><span>        </span><span>elif</span><span> </span><span>not</span><span> </span><span>self</span><span>.sunny:</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>result </span><span>=</span><span> </span><span>"Too dim."</span></div></div><div><div><div>25</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>result </span><span>=</span><span> </span><span>"UV is bad for your skin."</span></div></div><div><div><div>27</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(result)</span></span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>    </span><span>def</span><span> </span><span>_onchange_degrees_c</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>_param_name</span></span><span>,</span><span><span> </span><span>_old</span></span><span>,</span><span><span> </span><span>new</span></span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>30</div></div><div><span>        </span><span># if it's over 40C, it's gotta be sunny, right?</span></div></div><div><div><div>31</div></div><div><span>        </span><span>if</span><span><span> new </span><span>&gt;</span><span> </span></span><span>40</span><span>:</span></div></div><div><div><div>32</div></div><div><span>            </span><span>self</span><span><span>.sunny </span><span>=</span><span> </span></span><span>True</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>36</div></div><div><span>    </span><span>import</span><span> sys</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>c </span><span>=</span><span> </span><span>EnvironmentApp</span><span>()</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(c.</span><span>cmdloop</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如果你想在设置更改时收到通知，请确保为 <code>cmd2.utils.Settable</code> 的 <code>onchange_cb</code> 参数提供一个方法。此方法将在用户更改设置后调用，并接收旧值和新值。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(Cmd) set | grep sunny</span></div></div><div><div><div>2</div></div><div><span>sunny                 False                           Is it sunny outside?</span></div></div><div><div><div>3</div></div><div><span>(Cmd) set | grep degrees</span></div></div><div><div><div>4</div></div><div><span>degrees_c             22                              Temperature in Celsius</span></div></div><div><div><div>5</div></div><div><span>(Cmd) sunbathe</span></div></div><div><div><div>6</div></div><div><span>Too dim.</span></div></div><div><div><div>7</div></div><div><span>(Cmd) set degrees_c 41</span></div></div><div><div><div>8</div></div><div><span>degrees_c - was: 22</span></div></div><div><div><div>9</div></div><div><span>now: 41</span></div></div><div><div><div>10</div></div><div><span>(Cmd) set sunny</span></div></div><div><div><div>11</div></div><div><span>sunny: True</span></div></div><div><div><div>12</div></div><div><span>(Cmd) sunbathe</span></div></div><div><div><div>13</div></div><div><span>UV is bad for your skin.</span></div></div><div><div><div>14</div></div><div><span>(Cmd) set degrees_c 13</span></div></div><div><div><div>15</div></div><div><span>degrees_c - was: 41</span></div></div><div><div><div>16</div></div><div><span>now: 13</span></div></div><div><div><div>17</div></div><div><span>(Cmd) sunbathe</span></div></div><div><div><div>18</div></div><div><span>It's 13 C - are you a penguin?</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>隐藏设置<a href="#隐藏设置"><span>#</span></a></h2><p>你可能希望阻止用户修改内置设置。假设你永远不希望程序的最终用户能够在发生错误时启用完整的调试回溯。你可能想要隐藏 <a href="#debug">debug</a> 设置。<code>cmd2.Cmd.remove_settable</code> 方法使这变得简单：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyApp</span><span>(</span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>5</div></div><div><span>        </span><span>self</span><span><span>.</span><span>remove_settable</span><span>(</span></span><span>'debug'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>插件<a href="#插件"><span>#</span></a></h1><p><code>cmd2</code> 有一个内置的插件框架，允许开发者创建 <code>cmd2</code> 插件来扩展基本 <code>cmd2</code> 功能，并可被多个应用使用。</p><p>有多种方式可以使用插件向 <code>cmd2</code> 添加功能。大多数插件将作为混入（mixin）实现。混入是一个将代码封装并注入到另一个类中的类。在 <code>cmd2</code> 项目中使用插件的开发者将把插件的代码注入到他们的 [cmd2.Cmd][] 子类中。</p><section><h2>混入与初始化<a href="#混入与初始化"><span>#</span></a></h2><p>以下简短示例展示了如何混入插件以及插件如何被初始化。</p><p>插件代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyPlugin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># code placed here runs before cmd2.Cmd initializes</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># code placed here runs after cmd2.Cmd initializes</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用插件的示例应用：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> cmd2</span></div></div><div><div><div>2</div></div><div><span>import</span><span> cmd2_myplugin</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>Example</span><span>(</span><span>cmd2_myplugin</span><span>.</span><span>MyPlugin</span><span>, </span><span>cmd2</span><span>.</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""An class to show how to use a plugin"""</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span># code placed here runs before cmd2.Cmd or</span></div></div><div><div><div>10</div></div><div><span>        </span><span># any plugins initialize</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>12</div></div><div><span>        </span><span># code placed here runs after cmd2.Cmd and</span></div></div><div><div><div>13</div></div><div><span>        </span><span># all plugins have initialized</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>插件必须在 <code>cmd2.Cmd</code> 之前被继承（或混入）。这有两个原因：</p><ul>
<li>Python 标准库中的 <code>cmd.Cmd.__init__</code> 方法不调用 <code>super().__init__()</code>。由于这个疏忽，如果你不先从 <code>MyPlugin</code> 继承，<code>MyPlugin.__init__()</code> 方法将永远不会被调用。</li>
<li>你可能希望你的插件能够重写 <code>cmd2.Cmd</code> 的方法。如果你在 <code>cmd2.Cmd</code> 之后混入插件，Python 方法解析顺序将先调用 <code>cmd2.Cmd</code> 的方法，然后才调用插件中的方法。</li>
</ul></div></div></section><section><h2>添加命令<a href="#添加命令"><span>#</span></a></h2><p>你的插件可以添加用户可见的命令。在插件中的操作方式与在 <code>cmd2.Cmd</code> 应用中相同：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyPlugin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>do_say</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>statement</span></span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>"""Simple say command"""</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(statement)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你在插件中拥有与在 <code>cmd2.Cmd</code> 应用中相同的所有能力，包括通过装饰器进行参数解析和自定义帮助方法。</p></section><section><h2>添加（或隐藏）设置<a href="#添加或隐藏设置"><span>#</span></a></h2><p>插件可以向应用添加用户可控的设置。示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyPlugin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># code placed here runs before cmd2.Cmd initializes</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># code placed here runs after cmd2.Cmd initializes</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.mysetting </span><span>=</span><span> </span></span><span>"somevalue"</span></div></div><div><div><div>7</div></div><div><span>        </span><span>self</span><span><span>.</span><span>add_settable</span><span>(cmd2.</span><span>Settable</span><span>(</span></span><span>"mysetting"</span><span>, </span><span>str</span><span>, </span><span>"short help message for mysetting"</span><span>, </span><span>self</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你可以通过调用 <code>cmd2.Cmd.remove_settable</code> 来对用户隐藏设置。详见<a href="../settings_plugins/#%E8%AE%BE%E7%BD%AE">设置</a>。</p></section><section><h2>装饰器<a href="#装饰器"><span>#</span></a></h2><p>你的插件可以提供装饰器，插件用户可以使用它来将自己的功能包装在他们自己的命令周围。</p></section><section><h2>重写方法<a href="#重写方法"><span>#</span></a></h2><p>你的插件可以重写核心 <code>cmd2.Cmd</code> 方法，改变它们的行为。这种方法应该谨慎使用，因为它非常脆弱。如果开发者选择在应用中使用多个插件，并且其中几个插件重写了同一个方法，只有第一个被混入的插件的重写方法会被调用。</p><p>钩子是更好的方法。</p></section><section><h2>钩子<a href="#钩子"><span>#</span></a></h2><p>插件可以注册钩子方法，这些方法由 <code>cmd2.Cmd</code> 在应用和命令处理生命周期的各个点调用。插件不应重写任何 <code>cmd</code> 基类钩子方法，而应按照<a href="../hooks/">钩子</a>部分所述注册它们的钩子。</p><p>你应该以插件名称开头来命名你的钩子。钩子方法会被混入 <code>cmd2</code> 应用中，这种命名约定有助于避免意外的方法重写。</p><p>简单示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>MyPlugin</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span># code placed here runs before cmd2 initializes</span></div></div><div><div><div>4</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(*args, **kwargs)</span></div></div><div><div><div>5</div></div><div><span>        </span><span># code placed here runs after cmd2 initializes</span></div></div><div><div><div>6</div></div><div><span>        </span><span># this is where you register any hook functions</span></div></div><div><div><div>7</div></div><div><span>        </span><span>self</span><span><span>.</span><span>register_postparsing_hook</span><span>(</span></span><span>self</span><span>.cmd2_myplugin_postparsing_hook)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>def</span><span> </span><span>cmd2_myplugin_postparsing_hook</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>data</span></span><span>:</span><span> cmd2.plugin.PostparsingData</span><span>) -&gt; cmd2.plugin.PostparsingData:</span></div></div><div><div><div>10</div></div><div><span>        </span><span>"""Method to be called after parsing user input, but before running the command"""</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.</span><span>poutput</span><span>(</span></span><span>"in postparsing_hook"</span><span>)</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span> data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注册允许多个插件（甚至应用本身）各自注入在应用或命令处理生命周期中被调用的代码。</p><p>参阅<a href="../hooks/">钩子</a>文档了解应用和命令生命周期的完整细节，包括所有可用钩子和钩子影响生命周期的方式。</p></section><section><h2>类和函数<a href="#类和函数"><span>#</span></a></h2><p>你的插件还可以提供 <code>cmd2</code> 应用开发者可以使用的类和函数。在文档中描述这些类和函数，以便插件用户知道有哪些可用。</p></section><section><h2>示例<a href="#示例"><span>#</span></a></h2><p>参阅 <a href="https://github.com/python-cmd2/cmd2-plugin-template" target="_blank">cmd2 插件模板</a>了解更多信息。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/cmd2/features/shortcuts_aliases_macros/</id>
      <title type="text">Python cmd2：快捷方式、别名与宏</title>
      <published>2026-05-28T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/cmd2/features/shortcuts_aliases_macros/"/>
      <summary type="text">介绍 cmd2 的快捷方式、别名和宏功能，帮助用户更高效地使用命令行。</summary>
      <content type="html"><![CDATA[<section><h1>快捷方式、别名与宏<a href="#快捷方式别名与宏"><span>#</span></a></h1><section><h2>快捷方式<a href="#快捷方式"><span>#</span></a></h2><p>长命令名称和常用命令的快捷方式可以为用户提供更多便利。快捷方式使用时与其参数之间没有空格分隔，如 <code>!ls</code>。默认情况下，定义了以下快捷方式：</p><ul>
<li><strong><code>?</code></strong> - help</li>
<li><strong><code>!</code></strong> - shell：以操作系统级别命令运行</li>
<li><strong><code>@</code></strong> - 运行脚本文件</li>
<li><strong><code>@@</code></strong> - 运行脚本文件；文件名相对于当前脚本位置</li>
</ul><p>要定义更多快捷方式，从 <code>cmd2.DEFAULT_SHORTCUTS</code> 常量（一个字典）开始，然后通过使用格式为 <code>{'shortcut': 'command_name'}</code> 的额外快捷方式字典更新它来添加更多快捷方式，其中命令名称省略 <code>do_</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>App</span><span>(</span><span>Cmd</span><span>)</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>shortcuts </span><span>=</span><span> cmd2.</span><span>DEFAULT_SHORTCUTS</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>shortcuts.</span><span>update</span><span>({</span><span>"*"</span><span>: </span><span>"sneeze"</span><span>, </span><span>"~"</span><span>: </span><span>"squirm"</span><span>})</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>cmd2.Cmd.</span><span>__init__</span><span>(</span><span>self</span><span>, </span><span>shortcuts</span><span><span>=</span><span>shortcuts)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Warning</div></div><div><p>快捷方式需要在调用 <code>cmd2.Cmd</code> 超类 <code>__init__()</code> 方法之前通过更新 <code>shortcuts</code> 字典属性来创建。此外，超类 init 方法需要在更新 <code>shortcuts</code> 属性之后调用。</p><p>此警告通常适用于许多其他在运行时不可设置的属性。</p></div></div><div><div><div></div><div>Note</div></div><div><p>命令、别名和宏的名称不能以快捷方式开头。</p></div></div></section><section><h2>别名<a href="#别名"><span>#</span></a></h2><p>除了快捷方式，<code>cmd2</code> 还通过 <code>alias</code> 命令提供别名功能。别名的工作方式类似于 Bash shell 中的别名。</p><p>创建别名的语法是：<code>alias create name command [args]</code>，例如 <code>alias create ls !ls -lF</code>。</p><p>重定向器和管道应在别名定义中加引号，以防止 <code>alias create</code> 命令被重定向：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>alias create save_results print_results "&gt;" out.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Tab 补全能识别别名，并像命令行上的别名命令一样进行补全。</p><p>更多详情请运行：<code>help alias create</code></p><p>使用 <code>alias list</code> 查看所有或部分别名。此命令的输出以可用于创建它们的格式显示你的别名。因此你可以将此输出放在 <code>cmd2</code> 启动脚本中，以便每次启动应用时重新创建你的别名。</p><p>使用 <code>alias delete</code> 删除别名。</p><p>更多详情请运行：<code>help alias delete</code></p><div><div><div></div><div>Note</div></div><div><p>别名不能与命令或宏同名。</p></div></div></section><section><h2>宏<a href="#宏"><span>#</span></a></h2><p><code>cmd2</code> 提供了一种类似于别名的功能，称为宏。宏和别名之间的主要区别是宏可以包含参数占位符。参数在创建宏时使用 <code>{#}</code> 表示法表达，其中 <code>{1}</code> 表示第一个参数。</p><p>以下创建了一个名为 <code>my_macro</code> 的宏，它期望两个参数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>macro create my_macro make_dinner -meat {1} -veggie {2}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当宏被调用时，提供的参数会被替换，组装好的命令会被运行。例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>my_macro beef broccoli ---&gt; make_dinner -meat beef -veggie broccoli</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>与别名类似，管道和重定向器需要在宏的定义中加引号：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>macro create lc !cat "{1}" "|" less</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要在命令中使用字面字符串 <code>{1}</code>，请这样转义：<code>{{1}}</code>。</p><p>由于宏在你按 <code>&lt;Enter&gt;</code> 后才解析，它们的参数会作为路径进行 Tab 补全。你可以通过重写 <code>Cmd.macro_arg_complete()</code> 来实现宏参数的自定义 Tab 补全，从而更改此默认行为。</p><p>更多详情请运行：<code>help macro create</code></p><p>macro 命令有 <code>list</code> 和 <code>delete</code> 子命令，其功能与同名的 alias 子命令相同。与别名一样，宏可以通过 <code>cmd2</code> 启动脚本创建，以便在应用会话之间保留它们。</p><p>更多关于列出宏的详情请运行：<code>help macro list</code></p><p>更多关于删除宏的详情请运行：<code>help macro delete</code></p><div><div><div></div><div>Note</div></div><div><p>宏不能与命令或别名同名。</p></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/csharp-ihttpclientfactory/</id>
      <title type="text">C# IHttpClientFactory</title>
      <published>2026-05-16T00:00:00.000Z</published>
      <updated>2026-05-16T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/csharp-ihttpclientfactory/"/>
      <summary type="text">详细介绍 C# 中 IHttpClientFactory 的概念、优势、使用方法和最佳实践，帮助开发者正确使用 HttpClient 进行 HTTP 请求。</summary>
      <content type="html"><![CDATA[<section><h1>C# 中 IHttpClientFactory 的介绍和最佳实践<a href="#c-中-ihttpclientfactory-的介绍和最佳实践"><span>#</span></a></h1><p>在 .NET 应用程序中进行 HTTP 请求时，<code>HttpClient</code> 是最常用的类。然而，直接使用 <code>HttpClient</code> 可能会导致一些问题，如端口耗尽和 DNS 更新问题。<code>IHttpClientFactory</code> 是 .NET Core 2.1 引入的一个接口，旨在解决这些问题并提供更好的 HTTP 客户端管理。</p><section><h2>1. 为什么需要 IHttpClientFactory？<a href="#1-为什么需要-ihttpclientfactory"><span>#</span></a></h2><section><h3>1.1 直接使用 HttpClient 的问题<a href="#11-直接使用-httpclient-的问题"><span>#</span></a></h3><ol>
<li><strong>端口耗尽</strong>：每次创建新的 <code>HttpClient</code> 实例都会创建新的套接字连接，可能导致端口耗尽。</li>
<li><strong>DNS 更新问题</strong>：<code>HttpClient</code> 默认会缓存 DNS 记录，即使 DNS 记录发生变化，也不会立即更新。</li>
<li><strong>资源管理</strong>：需要手动管理 <code>HttpClient</code> 的生命周期，容易导致资源泄漏。</li>
</ol></section><section><h3>1.2 IHttpClientFactory 的优势<a href="#12-ihttpclientfactory-的优势"><span>#</span></a></h3><ol>
<li><strong>集中管理</strong>：通过工厂模式集中管理 <code>HttpClient</code> 实例的创建和配置。</li>
<li><strong>生命周期管理</strong>：自动管理底层 <code>HttpMessageHandler</code> 的生命周期，避免端口耗尽问题。</li>
<li><strong>可配置性</strong>：支持命名客户端和类型化客户端，便于配置不同的 HTTP 客户端。</li>
<li><strong>依赖注入友好</strong>：与 ASP.NET Core 的依赖注入系统无缝集成。</li>
<li><strong>中间件支持</strong>：可以轻松添加日志、重试、熔断等中间件。</li>
</ol></section></section><section><h2>2. 配置 IHttpClientFactory<a href="#2-配置-ihttpclientfactory"><span>#</span></a></h2><section><h3>2.1 在 ASP.NET Core 中配置<a href="#21-在-aspnet-core-中配置"><span>#</span></a></h3><p>在 <code>Program.cs</code> 或 <code>Startup.cs</code> 中添加服务：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// Program.cs (.NET 6+)</span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>builder</span><span> </span><span>=</span><span> </span><span>WebApplication</span><span>.</span></span><span>CreateBuilder</span><span><span>(</span><span>args</span><span>);</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>// 添加 HttpClient 工厂服务</span></div></div><div><div><div>5</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>();</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>var</span><span><span> </span><span>app</span><span> </span><span>=</span><span> </span><span>builder</span><span>.</span></span><span>Build</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2.2 在控制台应用中配置<a href="#22-在控制台应用中配置"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>using</span><span> </span><span>Microsoft</span><span>.</span><span>Extensions</span><span>.</span><span>DependencyInjection</span><span>;</span></div></div><div><div><div>2</div></div><div><span>using</span><span> </span><span>Microsoft</span><span>.</span><span>Extensions</span><span>.</span><span>Hosting</span><span>;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>var</span><span><span> </span><span>host</span><span> </span><span>=</span><span> </span><span>Host</span><span>.</span></span><span>CreateDefaultBuilder</span><span><span>(</span><span>args</span><span>)</span></span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>.</span><span>ConfigureServices</span><span><span>((</span><span>context</span><span>, </span><span>services</span><span>) =&gt;</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>services</span><span>.</span><span>AddHttpClient</span><span>();</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>})</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>.</span><span>Build</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>3. 使用 IHttpClientFactory<a href="#3-使用-ihttpclientfactory"><span>#</span></a></h2><section><h3>3.1 基本用法<a href="#31-基本用法"><span>#</span></a></h3><p>通过依赖注入 <code>IHttpClientFactory</code> 并创建客户端：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>MyService</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>private</span><span> </span><span>readonly</span><span> </span><span>IHttpClientFactory</span><span> </span><span>_httpClientFactory</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>MyService</span><span>(</span><span>IHttpClientFactory</span><span><span> </span><span>httpClientFactory</span><span>)</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>_httpClientFactory</span><span> </span><span>=</span><span> </span><span>httpClientFactory</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>public</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>string</span><span>&gt; </span><span>GetDataAsync</span><span>()</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>12</div></div><div><span>        </span><span>var</span><span><span> </span><span>client</span><span> </span><span>=</span><span> </span><span>_httpClientFactory</span><span>.</span></span><span>CreateClient</span><span>();</span></div></div><div><div><div>13</div></div><div><span>        </span><span>var</span><span><span> </span><span>response</span><span> </span><span>=</span><span> await </span><span>client</span><span>.</span></span><span>GetAsync</span><span>(</span><span>"https://api.example.com/data"</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>response</span><span>.</span><span>EnsureSuccessStatusCode</span><span>();</span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span><span><span> await </span><span>response</span><span>.</span><span>Content</span><span>.</span></span><span>ReadAsStringAsync</span><span>();</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>3.2 命名客户端<a href="#32-命名客户端"><span>#</span></a></h3><p>为不同的服务配置不同的客户端：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 配置服务</span></div></div><div><div><div>2</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>(</span><span>"GitHub"</span><span><span>, </span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>3</div></div><div><span>{</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.github.com/"</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"Accept"</span><span>, </span><span>"application/vnd.github.v3+json"</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"User-Agent"</span><span>, </span><span>"HttpClientFactory-Sample"</span><span>);</span></div></div><div><div><div>7</div></div><div><span>});</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>// 使用命名客户端</span></div></div><div><div><div>10</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>GitHubService</span></div></div><div><div><div>11</div></div><div><span>{</span></div></div><div><div><div>12</div></div><div><span>    </span><span>private</span><span> </span><span>readonly</span><span> </span><span>IHttpClientFactory</span><span> </span><span>_httpClientFactory</span><span>;</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>public</span><span> </span><span>GitHubService</span><span>(</span><span>IHttpClientFactory</span><span><span> </span><span>httpClientFactory</span><span>)</span></span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>_httpClientFactory</span><span> </span><span>=</span><span> </span><span>httpClientFactory</span><span>;</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>public</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>string</span><span>&gt; </span><span>GetRepositoriesAsync</span><span>()</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>21</div></div><div><span>        </span><span>var</span><span><span> </span><span>client</span><span> </span><span>=</span><span> </span><span>_httpClientFactory</span><span>.</span></span><span>CreateClient</span><span>(</span><span>"GitHub"</span><span>);</span></div></div><div><div><div>22</div></div><div><span>        </span><span>var</span><span><span> </span><span>response</span><span> </span><span>=</span><span> await </span><span>client</span><span>.</span></span><span>GetAsync</span><span>(</span><span>"repositories"</span><span>);</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>response</span><span>.</span><span>EnsureSuccessStatusCode</span><span>();</span></div></div><div><div><div>24</div></div><div><span>        </span><span>return</span><span><span> await </span><span>response</span><span>.</span><span>Content</span><span>.</span></span><span>ReadAsStringAsync</span><span>();</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>3.3 类型化客户端<a href="#33-类型化客户端"><span>#</span></a></h3><p>创建强类型的客户端类，这是推荐的方式：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 定义类型化客户端</span></div></div><div><div><div>2</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>GitHubClient</span></div></div><div><div><div>3</div></div><div><span>{</span></div></div><div><div><div>4</div></div><div><span>    </span><span>private</span><span> </span><span>readonly</span><span> </span><span>HttpClient</span><span> </span><span>_httpClient</span><span>;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>public</span><span> </span><span>GitHubClient</span><span>(</span><span>HttpClient</span><span><span> </span><span>httpClient</span><span>)</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>_httpClient</span><span> </span><span>=</span><span> </span><span>httpClient</span><span>;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>public</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>List</span><span>&lt;</span><span>Repository</span><span>&gt;&gt; </span><span>GetRepositoriesAsync</span><span>()</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>13</div></div><div><span>        </span><span>var</span><span><span> </span><span>response</span><span> </span><span>=</span><span> await </span><span>_httpClient</span><span>.</span></span><span>GetAsync</span><span>(</span><span>"repositories"</span><span>);</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>response</span><span>.</span><span>EnsureSuccessStatusCode</span><span>();</span></div></div><div><div><div>15</div></div><div><span>        </span><span>var</span><span><span> </span><span>content</span><span> </span><span>=</span><span> await </span><span>response</span><span>.</span><span>Content</span><span>.</span></span><span>ReadAsStringAsync</span><span>();</span></div></div><div><div><div>16</div></div><div><span>        </span><span>return</span><span><span> </span><span>JsonSerializer</span><span>.</span></span><span>Deserialize</span><span>&lt;</span><span>List</span><span>&lt;</span><span>Repository</span><span><span>&gt;&gt;(</span><span>content</span><span>);</span></span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>// 配置类型化客户端</span></div></div><div><div><div>21</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>&lt;</span><span>GitHubClient</span><span><span>&gt;(</span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>22</div></div><div><span>{</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.github.com/"</span><span>);</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"Accept"</span><span>, </span><span>"application/vnd.github.v3+json"</span><span>);</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"User-Agent"</span><span>, </span><span>"HttpClientFactory-Sample"</span><span>);</span></div></div><div><div><div>26</div></div><div><span>});</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>// 使用类型化客户端</span></div></div><div><div><div>29</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>MyController</span><span> : </span><span>ControllerBase</span></div></div><div><div><div>30</div></div><div><span>{</span></div></div><div><div><div>31</div></div><div><span>    </span><span>private</span><span> </span><span>readonly</span><span> </span><span>GitHubClient</span><span> </span><span>_gitHubClient</span><span>;</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>    </span><span>public</span><span> </span><span>MyController</span><span>(</span><span>GitHubClient</span><span><span> </span><span>gitHubClient</span><span>)</span></span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>_gitHubClient</span><span> </span><span>=</span><span> </span><span>gitHubClient</span><span>;</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span>public</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>IActionResult</span><span>&gt; </span><span>GetRepositories</span><span>()</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>40</div></div><div><span>        </span><span>var</span><span><span> </span><span>repositories</span><span> </span><span>=</span><span> await </span><span>_gitHubClient</span><span>.</span></span><span>GetRepositoriesAsync</span><span>();</span></div></div><div><div><div>41</div></div><div><span>        </span><span>return</span><span> </span><span>Ok</span><span><span>(</span><span>repositories</span><span>);</span></span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h2>4. 最佳实践<a href="#4-最佳实践"><span>#</span></a></h2><section><h3>4.1 优先使用类型化客户端<a href="#41-优先使用类型化客户端"><span>#</span></a></h3><p>类型化客户端提供了以下优势：</p><ul>
<li>强类型支持，编译时检查</li>
<li>代码更清晰，易于维护</li>
<li>可以封装业务逻辑</li>
</ul></section><section><h3>4.2 正确配置 HttpClient<a href="#42-正确配置-httpclient"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>&lt;</span><span>GitHubClient</span><span><span>&gt;(</span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>// 设置合理的超时时间</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>Timeout</span><span> </span><span>=</span><span> </span><span>TimeSpan</span><span>.</span><span>FromSeconds</span><span>(</span><span>30</span><span>);</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>// 设置基础地址</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.github.com/"</span><span>);</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>// 设置默认请求头</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"Accept"</span><span>, </span><span>"application/vnd.github.v3+json"</span><span>);</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"User-Agent"</span><span>, </span><span>"YourApp-Name"</span><span>);</span></div></div><div><div><div>12</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4.2 避免反模式<a href="#42-避免反模式"><span>#</span></a></h3><ol>
<li><strong>不要</strong>在每次请求时创建新的 <code>HttpClient</code> 实例</li>
<li><strong>不要</strong>在 <code>using</code> 块中使用 <code>HttpClient</code>（除非使用 <code>IHttpClientFactory</code>）</li>
<li><strong>不要</strong>手动管理 <code>HttpClient</code> 的生命周期</li>
</ol></section></section><section><h2>5. 高级配置<a href="#5-高级配置"><span>#</span></a></h2><section><h3>5.1 自定义消息处理程序<a href="#51-自定义消息处理程序"><span>#</span></a></h3><p>创建自定义的 <code>DelegatingHandler</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>LoggingHandler</span><span> : </span><span>DelegatingHandler</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>private</span><span> </span><span>readonly</span><span> </span><span>ILogger</span><span>&lt;</span><span>LoggingHandler</span><span>&gt; </span><span>_logger</span><span>;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>LoggingHandler</span><span>(</span><span>ILogger</span><span>&lt;</span><span>LoggingHandler</span><span><span>&gt; </span><span>logger</span><span>)</span></span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>_logger</span><span> </span><span>=</span><span> </span><span>logger</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>protected</span><span> </span><span>override</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>HttpResponseMessage</span><span>&gt; </span><span>SendAsync</span><span>(</span></div></div><div><div><div>11</div></div><div><span>        </span><span>HttpRequestMessage</span><span><span> </span><span>request</span><span>, </span></span><span>CancellationToken</span><span><span> </span><span>cancellationToken</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>_logger</span><span>.</span><span>LogInformation</span><span>(</span><span>$"Sending request to </span><span>{</span><span>request</span><span>.</span><span>RequestUri</span><span>}</span><span>"</span><span>);</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>        </span><span>var</span><span><span> </span><span>response</span><span> </span><span>=</span><span> await </span></span><span>base</span><span>.</span><span>SendAsync</span><span><span>(</span><span>request</span><span>, </span><span>cancellationToken</span><span>);</span></span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>_logger</span><span>.</span><span>LogInformation</span><span>(</span><span>$"Response status: </span><span>{</span><span>response</span><span>.</span><span>StatusCode</span><span>}</span><span>"</span><span>);</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>        </span><span>return</span><span><span> </span><span>response</span><span>;</span></span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>}</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>// 注册处理程序</span></div></div><div><div><div>24</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddTransient</span><span>&lt;</span><span>LoggingHandler</span><span>&gt;();</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>&lt;</span><span>GitHubClient</span><span><span>&gt;(</span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>27</div></div><div><span>{</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.github.com/"</span><span>);</span></div></div><div><div><div>29</div></div><div><span>})</span></div></div><div><div><div>30</div></div><div><span>.</span><span>AddHttpMessageHandler</span><span>&lt;</span><span>LoggingHandler</span><span>&gt;();</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>5.2 多个客户端配置<a href="#52-多个客户端配置"><span>#</span></a></h3><p>为不同的服务配置不同的客户端：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// GitHub 客户端</span></div></div><div><div><div>2</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>&lt;</span><span>GitHubClient</span><span><span>&gt;(</span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>3</div></div><div><span>{</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.github.com/"</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>DefaultRequestHeaders</span><span>.</span><span>Add</span><span>(</span><span>"Accept"</span><span>, </span><span>"application/vnd.github.v3+json"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>});</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>// 其他 API 客户端</span></div></div><div><div><div>9</div></div><div><span><span>builder</span><span>.</span><span>Services</span><span>.</span></span><span>AddHttpClient</span><span>&lt;</span><span>PaymentService</span><span><span>&gt;(</span><span>client</span><span> =&gt;</span></span></div></div><div><div><div>10</div></div><div><span>{</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>BaseAddress</span><span> </span><span>=</span><span> new </span><span>Uri</span><span>(</span><span>"https://api.payment.com/"</span><span>);</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>client</span><span>.</span><span>Timeout</span><span> </span><span>=</span><span> </span><span>TimeSpan</span><span>.</span><span>FromSeconds</span><span>(</span><span>60</span><span>);</span></div></div><div><div><div>13</div></div><div><span>});</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>6. 总结<a href="#6-总结"><span>#</span></a></h2><p><code>IHttpClientFactory</code> 是 .NET 中管理 <code>HttpClient</code> 的最佳方式，它解决了直接使用 <code>HttpClient</code> 的各种问题，并提供了以下优势：</p><ol>
<li><strong>资源管理</strong>：自动管理 <code>HttpMessageHandler</code> 的生命周期，避免端口耗尽</li>
<li><strong>配置灵活性</strong>：支持命名客户端和类型化客户端</li>
<li><strong>依赖注入集成</strong>：与 ASP.NET Core 的 DI 系统无缝集成</li>
<li><strong>中间件支持</strong>：可以轻松添加日志、重试、熔断等中间件</li>
<li><strong>最佳实践</strong>：遵循 Microsoft 推荐的 HTTP 客户端使用模式</li>
</ol><p>在开发新的 .NET 应用程序时，强烈建议使用 <code>IHttpClientFactory</code> 而不是直接创建 <code>HttpClient</code> 实例。这不仅提高了应用程序的可靠性，还使代码更易于维护和测试。</p></section><section><h2>参考资料<a href="#参考资料"><span>#</span></a></h2><ul>
<li><a href="https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests" target="_blank">Microsoft 官方文档：IHttpClientFactory</a></li>
<li><a href="https://docs.microsoft.com/zh-cn/dotnet/fundamentals/networking/http/httpclient-guidelines" target="_blank">HttpClient 使用指南</a></li>
</ul></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/poco/</id>
      <title type="text">POCO（Plain Old CLR Object）简介</title>
      <published>2026-03-22T00:00:00.000Z</published>
      <updated>2026-03-22T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/poco/"/>
      <summary type="text">介绍 POCO（Plain Old CLR Object）的概念、特征以及在现代 C# 开发中的应用，特别是在领域驱动设计（DDD）和 ORM（如 Entity Framework Core）中的重要性。</summary>
      <content type="html"><![CDATA[<p>POCO 是 Plain Old CLR Object 的缩写，直译为“简单的旧 CLR 对象”。</p>
<p>这个术语借鉴于 Java 世界的 POJO (Plain Old Java Object)。它的核心思想是：一个纯粹的、简单的 .NET 对象，不依赖于任何特定的框架、基类或接口。</p>
<ol>
<li>
<p>POCO 的核心特征
一个标准的 POCO 类通常具有以下特征：</p>
<ol>
<li>没有继承特定基类：它不继承自像 MarshalByRefObject、Entity、Controller 这样的框架特定基类。通常直接继承自
System.Object（虽然在 C# 中这通常是隐式的）。</li>
<li>没有实现特定接口：它不需要实现像 INotifyPropertyChanged、IDataErrorInfo 或 IEntity 这样的接口（尽管在实际项目中为了方便，有时会妥协实现这些接口）。</li>
<li>仅包含数据：主要由属性（Properties）、字段（Fields）和简单的业务逻辑（如验证）组成。</li>
<li>持久化无关（Persistence Ignorant）：POCO 类不知道数据是如何存储的（是存到 SQL Server、MySQL 还是 XML 文件）。它只是数据的容器。</li>
</ol>
</li>
<li>
<p>代码示例</p>
<p>✅ 这是一个标准的 POCO 类：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>User</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>public</span><span> </span><span>int</span><span> </span><span>Id</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>4</div></div><div><span>    </span><span>public</span><span> </span><span>string</span><span> </span><span>Name</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>string</span><span> </span><span>Email</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>6</div></div><div><span>    </span><span>public</span><span> </span><span>DateTime</span><span> </span><span>CreatedDate</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>// 可以包含简单的业务逻辑，但不涉及数据库或UI</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>bool</span><span> </span><span>IsValidEmail</span><span>()</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>11</div></div><div><span>        </span><span>return</span><span><span> </span><span>Email</span><span>.</span></span><span>Contains</span><span>(</span><span>"@"</span><span>);</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>13</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p><strong>特点</strong>：</p>
<ul>
<li>只有自动实现的属性。
<ul>
<li>没有继承任何东西。</li>
<li>没有实现任何接口。</li>
<li>可以在任何地方（控制台、Web、桌面应用）使用，不依赖 ASP.NET 或 Entity Framework。</li>
</ul>
</li>
</ul>
<p>❌ 这不是 POCO 类（依赖框架）：</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 依赖 Entity Framework Core</span></div></div><div><div><div>2</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>User</span><span> : </span><span>EntityBase</span></div></div><div><div><div>3</div></div><div><span>{</span></div></div><div><div><div>4</div></div><div><span>    </span><span>public</span><span> </span><span>int</span><span> </span><span>Id</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>string</span><span> </span><span>Name</span><span> { </span><span>get</span><span>; </span><span>set</span><span>; }</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>// 依赖 WPF/WinForms 数据绑定</span></div></div><div><div><div>9</div></div><div><span>public</span><span> </span><span>class</span><span> </span><span>User</span><span> : </span><span>INotifyPropertyChanged</span></div></div><div><div><div>10</div></div><div><span>{</span></div></div><div><div><div>11</div></div><div><span>    </span><span>private</span><span> </span><span>string</span><span> </span><span>_name</span><span>;</span></div></div><div><div><div>12</div></div><div><span>    </span><span>public</span><span> </span><span>string</span><span> </span><span>Name</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>14</div></div><div><span>        </span><span>get</span><span><span> =&gt; </span><span>_name</span><span>;</span></span></div></div><div><div><div>15</div></div><div><span>        </span><span>set</span><span><span> { </span><span>_name</span><span> </span><span>=</span><span> </span></span><span>value</span><span>; </span><span>OnPropertyChanged</span><span>(); }</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>// ... 大量的 INotifyPropertyChanged 实现代码</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
</li>
<li>
<p>为什么要使用 POCO？</p>
<ol>
<li>解耦（Decoupling）：
<ul>
<li>你的业务逻辑类不依赖于数据库框架（如 EF Core）或 UI 框架（如 WPF/Blazor）。</li>
<li>如果你想从 Entity Framework 换成 Dapper，或者从 MVC 换成 Minimal API，POCO 类完全不需要修改。</li>
</ul>
</li>
<li>可测试性（Testability）：
<ul>
<li>因为 POCO 不依赖外部框架，你可以非常容易地在单元测试中实例化它们并进行断言，而不需要模拟复杂的框架上下文。</li>
</ul>
</li>
<li>简单性：
<ul>
<li>代码更干净，更容易理解。它就是数据的“形状”，没有任何魔法。</li>
</ul>
</li>
<li>ORM 友好：
<ul>
<li>现代 ORM（如 Entity Framework Core）都推荐使用 POCO 作为实体类（Entity）。EF Core 通过约定（Convention）或 Fluent API
来映射
POCO 到数据库表，而不需要 POCO 本身做任何特殊处理。</li>
</ul>
</li>
</ol>
</li>
<li>
<p>POCO vs DTO (Data Transfer Object)
这两个概念经常一起出现，但有细微区别：</p>
<ul>
<li>POCO (领域模型/实体)：
<ul>
<li>通常对应数据库中的表。</li>
<li>可能包含业务逻辑（如计算、验证）。</li>
<li>例如：User 实体，可能有 ChangePassword() 方法。</li>
</ul>
</li>
<li>DTO (数据传输对象)：
<ul>
<li>专门用于在不同层（如 Controller 和 View，或 微服务之间）传输数据。</li>
<li>绝对没有业务逻辑，只有属性。</li>
<li>形状往往是为特定视图裁剪的（例如 UserCreateDto 可能没有 Id 字段）。
注意：在很多简单的项目中，POCO 和 DTO 是同一个类，但在复杂的领域驱动设计（DDD）中，它们是严格分开的。</li>
</ul>
</li>
</ul>
</li>
<li>
<p>现代 C# 中的 POCO：Record
在 C# 9 及更高版本中，引入了 record 类型，它是创建 POCO 的完美语法糖：</p>
</li>
</ol>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 这就是一个不可变的 POCO</span></div></div><div><div><div>2</div></div><div><span>public</span><span> </span><span>record</span><span> </span><span>User</span><span>(</span><span>int</span><span><span> </span><span>Id</span><span>, </span></span><span>string</span><span><span> </span><span>Name</span><span>, </span></span><span>string</span><span><span> </span><span>Email</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>总结
POCO 就是一个“干净”的 C# 类，它只关心“我有什么数据”，而不关心“数据怎么存”或“数据怎么显示”。 它是 .NET 中实现松耦合架构的基石。</p>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/avalonia-dynamic-background-image/</id>
      <title type="text">Avalonia 动态加载图片/动态修改背景图</title>
      <published>2025-11-29T00:00:00.000Z</published>
      <updated>2025-11-29T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/avalonia-dynamic-background-image/"/>
      <summary type="text">介绍在 Avalonia 框架中动态加载图片的几种常用方法，包括从本地资源、Web URL 和字节数组加载图片的实现步骤。</summary>
      <content type="html"><![CDATA[<section><h1>Avalonia 动态加载图片<a href="#avalonia-动态加载图片"><span>#</span></a></h1><p>在 Avalonia 中动态加载图片可以通过多种方式实现，支持从本地资源、Web URL 或字节流加载图片。以下是几种常用方法的实现步骤。</p><section><h2>1. 从本地资源加载图片<a href="#1-从本地资源加载图片"><span>#</span></a></h2><p>使用 AssetLoader 加载嵌入的资源文件。</p><section><h3>步骤<a href="#步骤"><span>#</span></a></h3><p>确保图片文件已添加到项目中，并设置为 AvaloniaResource。</p><p>使用以下代码加载图片：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>uri</span><span> </span><span>=</span><span> new </span></span><span>Uri</span><span>(</span><span>"avares://YourProjectName/Assets/image.jpg"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>bitmap</span><span> </span><span>=</span><span> new </span></span><span>Bitmap</span><span><span>(</span><span>AssetLoader</span><span>.</span></span><span>Open</span><span><span>(</span><span>uri</span><span>));</span></span></div></div><div><div><div>3</div></div><div><span><span>imageControl</span><span>.</span><span>Source</span><span> </span><span>=</span><span> </span><span>bitmap</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意：imageControl 是 Avalonia 的 Image 控件实例。</p><p>或者使用 MVVM 绑定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>Image</span><span> </span><span>Source</span><span>=</span><span>"{Binding ImageSource}"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 ViewModel 中设置 ImageSource 属性：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span><span> </span><span>Bitmap</span><span> </span><span>ImageSource</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>get</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>5</div></div><div><span>        </span><span>var</span><span><span> </span><span>uri</span><span> </span><span>=</span><span> new </span></span><span>Uri</span><span>(</span><span>"avares://YourProjectName/Assets/image.jpg"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>        </span><span>return</span><span> new </span><span>Bitmap</span><span><span>(</span><span>AssetLoader</span><span>.</span></span><span>Open</span><span><span>(</span><span>uri</span><span>));</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>2. 从 Web URL 加载图片<a href="#2-从-web-url-加载图片"><span>#</span></a></h2><p>通过 HttpClient 异步下载图片并加载。</p><section><h3>步骤<a href="#步骤-1"><span>#</span></a></h3><p>创建一个辅助类来处理网络请求：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>static</span><span> </span><span>async</span><span> </span><span>Task</span><span>&lt;</span><span>Bitmap</span><span>?&gt; </span><span>LoadFromWeb</span><span>(</span><span>Uri</span><span><span> </span><span>url</span><span>)</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>using</span><span> </span><span>var</span><span><span> </span><span>httpClient</span><span> </span><span>=</span><span> new </span></span><span>HttpClient</span><span>();</span></div></div><div><div><div>4</div></div><div><span>    </span><span>try</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>6</div></div><div><span>        </span><span>var</span><span><span> </span><span>response</span><span> </span><span>=</span><span> await </span><span>httpClient</span><span>.</span></span><span>GetAsync</span><span><span>(</span><span>url</span><span>);</span></span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>response</span><span>.</span><span>EnsureSuccessStatusCode</span><span>();</span></div></div><div><div><div>8</div></div><div><span>        </span><span>var</span><span><span> </span><span>data</span><span> </span><span>=</span><span> await </span><span>response</span><span>.</span><span>Content</span><span>.</span></span><span>ReadAsByteArrayAsync</span><span>();</span></div></div><div><div><div>9</div></div><div><span>        </span><span>return</span><span> new </span><span>Bitmap</span><span>(new </span><span>MemoryStream</span><span><span>(</span><span>data</span><span>));</span></span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>    </span><span>catch</span><span> (</span><span>Exception</span><span><span> </span><span>ex</span><span>)</span></span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>Console</span><span>.</span><span>WriteLine</span><span>(</span><span>$"Error loading image: </span><span>{</span><span>ex</span><span>.</span><span>Message</span><span>}</span><span>"</span><span>);</span></div></div><div><div><div>14</div></div><div><span>        </span><span>return</span><span> </span><span>null</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>在代码中调用该方法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>bitmap</span><span> </span><span>=</span><span> await </span><span>ImageHelper</span><span>.</span></span><span>LoadFromWeb</span><span>(new </span><span>Uri</span><span>(</span><span>"https://example.com/image.jpg"</span><span>));</span></div></div><div><div><div>2</div></div><div><span><span>imageControl</span><span>.</span><span>Source</span><span> </span><span>=</span><span> </span><span>bitmap</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者使用 MVVM 绑定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>async</span><span> </span><span>Task</span><span> </span><span>LoadImageAsync</span><span>()</span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>ImageSource</span><span> </span><span>=</span><span> await </span><span>ImageHelper</span><span>.</span><span>LoadFromWeb</span><span>(new </span><span>Uri</span><span>(</span><span>"https://example.com/image.jpg"</span><span>));</span></div></div><div><div><div>4</div></div><div><span>    </span><span>OnPropertyChanged</span><span><span>(nameof(</span><span>ImageSource</span><span>));</span></span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>public</span><span><span> </span><span>Bitmap</span><span>? </span><span>ImageSource</span><span> { </span><span>get</span><span>; </span></span><span>private</span><span><span> </span><span>set</span><span>; }</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>Image</span><span> </span><span>Source</span><span>=</span><span>"{Binding ImageSource}"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>3. 从字节数组加载图片<a href="#3-从字节数组加载图片"><span>#</span></a></h2><p>适用于动态生成或从外部获取的图像数据。</p><section><h3>步骤<a href="#步骤-2"><span>#</span></a></h3><p>将字节数组转换为 Bitmap：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span> </span><span>static</span><span> </span><span>Bitmap</span><span> </span><span>ByteToBitmap</span><span>(</span><span>byte</span><span><span>[] </span><span>bytes</span><span>)</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span> new </span><span>Bitmap</span><span>(new </span><span>MemoryStream</span><span><span>(</span><span>bytes</span><span>));</span></span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用该方法设置图片源：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>byte</span><span><span>[] </span><span>imageBytes</span><span> </span><span>=</span><span> </span><span>File</span><span>.</span></span><span>ReadAllBytes</span><span>(</span><span>"path/to/image.jpg"</span><span>);</span></div></div><div><div><div>2</div></div><div><span><span>imageControl</span><span>.</span><span>Source</span><span> </span><span>=</span><span> </span></span><span>ByteToBitmap</span><span><span>(</span><span>imageBytes</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者使用 MVVM 绑定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span><span> </span><span>Bitmap</span><span> </span><span>ImageSource</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>get</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>5</div></div><div><span>        </span><span>byte</span><span><span>[] </span><span>imageBytes</span><span> </span><span>=</span><span> </span><span>File</span><span>.</span></span><span>ReadAllBytes</span><span>(</span><span>"path/to/image.jpg"</span><span>);</span></div></div><div><div><div>6</div></div><div><span>        </span><span>return</span><span> </span><span>ByteToBitmap</span><span><span>(</span><span>imageBytes</span><span>);</span></span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>8</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>Image</span><span> </span><span>Source</span><span>=</span><span>"{Binding ImageSource}"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>4. 从文件路径加载图片<a href="#4-从文件路径加载图片"><span>#</span></a></h2><p>直接从文件系统加载图片。</p><section><h3>步骤<a href="#步骤-3"><span>#</span></a></h3><p>使用以下代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>bitmap</span><span> </span><span>=</span><span> new </span></span><span>Bitmap</span><span>(</span><span>"path/to/image.jpg"</span><span>);</span></div></div><div><div><div>2</div></div><div><span><span>imageControl</span><span>.</span><span>Source</span><span> </span><span>=</span><span> </span><span>bitmap</span><span>;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者使用 MVVM 绑定：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>public</span><span><span> </span><span>Bitmap</span><span> </span><span>ImageSource</span></span></div></div><div><div><div>2</div></div><div><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>get</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>{</span></div></div><div><div><div>5</div></div><div><span>        </span><span>return</span><span> new </span><span>Bitmap</span><span>(</span><span>"path/to/image.jpg"</span><span>);</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>Image</span><span> </span><span>Source</span><span>=</span><span>"{Binding ImageSource}"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/python-study/</id>
      <title type="text">Python 学习笔记</title>
      <published>2025-11-27T00:00:00.000Z</published>
      <updated>2025-11-27T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/python-study/"/>
      <summary type="text">这是一篇关于Python编程语言的学习笔记，涵盖了从基础到进阶的内容，包括变量、数据类型、条件语句、循环语句、函数以及文件操作等方面的知识。</summary>
      <content type="html"><![CDATA[<section><h1>学习<a href="#学习"><span>#</span></a></h1><section><h2>先决条件<a href="#先决条件"><span>#</span></a></h2><p>环境:</p><ul>
<li><a href="https://www.python.org/downloads/release/python-3115/" target="_blank">Python 3.11.5</a></li>
<li><a href="https://www.jetbrains.com/pycharm/download/" target="_blank">PyCharm Community Edition</a> 或 <a href="https://code.visualstudio.com/Download" target="_blank">Visual Studio Code</a></li>
<li>Windows 10 x64及以上版本</li>
<li>如果你有更喜欢的配置，可以尝试使用自己的配置。</li>
</ul><div><div><div></div><div>Tip</div></div><div><p>如果你是初学者，建议使用Visual Studio Code。</p><p>如果你考虑长期学习Python，那么PyCharm将是更好的选择</p></div></div></section><section><h2>基础<a href="#基础"><span>#</span></a></h2><p>Python 是一种高级编程语言，它的语法简单易懂，易于学习。</p><section><h3>变量、数据类型<a href="#变量数据类型"><span>#</span></a></h3><p>Python中的变量是用来存储数据的命名内存位置。与许多其他编程语言不同，Python是动态类型语言，这意味着你不需要在使用变量之前明确声明它的数据类型。Python解释器会根据你赋给变量的值自动推断其类型。Python支持多种内置数据类型，包括数字（整数、浮点数、复数）、字符串、布尔值、列表、元组、字典和集合等。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 数字类型 (Numbers)</span></div></div><div><div><div>2</div></div><div><span># 整数 (int): 表示没有小数部分的数字。</span></div></div><div><div><div>3</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>25</span><span>          </span><span># 这是一个整数变量，存储了数字25。</span></div></div><div><div><div>4</div></div><div><span># 浮点数 (float): 表示带有小数部分的数字。</span></div></div><div><div><div>5</div></div><div><span><span>height </span><span>=</span><span> </span></span><span>1.75</span><span>     </span><span># 这是一个浮点数变量，存储了数字1.75。</span></div></div><div><div><div>6</div></div><div><span># 复数 (complex): 表示复数，由实部和虚部组成，虚部用'j'或'J'表示。</span></div></div><div><div><div>7</div></div><div><span># complex_num = 1 + 2j # 这是一个复数变量。</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 字符串类型 (String)</span></div></div><div><div><div>10</div></div><div><span># 字符串 (str): 表示文本数据，由单引号、双引号或三引号包围。</span></div></div><div><div><div>11</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"小明"</span><span>     </span><span># 这是一个字符串变量，存储了"小明"。字符串是不可变的序列。</span></div></div><div><div><div>12</div></div><div><span><span>message </span><span>=</span><span> </span></span><span>'你好，世界！'</span><span> </span><span># 也可以使用单引号定义字符串。</span></div></div><div><div><div>13</div></div><div><span><span>long_text </span><span>=</span><span> </span></span><span>"""这是一个</span></div></div><div><div><div>14</div></div><div><span>多行字符串</span></div></div><div><div><div>15</div></div><div><span>示例。"""</span><span> </span><span># 三引号用于定义多行字符串。</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 布尔类型 (Boolean)</span></div></div><div><div><div>18</div></div><div><span># 布尔值 (bool): 表示真或假，只有两个值：True和False。常用于条件判断。</span></div></div><div><div><div>19</div></div><div><span><span>is_student </span><span>=</span><span> </span></span><span>True</span><span>  </span><span># 这是一个布尔变量，表示“是学生”。</span></div></div><div><div><div>20</div></div><div><span><span>has_license </span><span>=</span><span> </span></span><span>False</span><span> </span><span># 另一个布尔变量，表示“没有驾照”。</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span># 类型转换 (Type Conversion/Casting)</span></div></div><div><div><div>23</div></div><div><span># Python允许在不同数据类型之间进行转换，这在处理输入或特定操作时非常有用。</span></div></div><div><div><div>24</div></div><div><span><span>str_num </span><span>=</span><span> </span></span><span>"42"</span><span>       </span><span># 一个字符串，内容是数字"42"。</span></div></div><div><div><div>25</div></div><div><span><span>num </span><span>=</span><span> </span></span><span>int</span><span>(str_num)   </span><span># 使用int()函数将字符串"42"转换为整数42。现在num是一个整数。</span></div></div><div><div><div>26</div></div><div><span><span>float_num </span><span>=</span><span> </span></span><span>float</span><span>(</span><span>"3.14"</span><span>) </span><span># 使用float()函数将字符串"3.14"转换为浮点数3.14。</span></div></div><div><div><div>27</div></div><div><span><span>num_to_str </span><span>=</span><span> </span></span><span>str</span><span>(</span><span>123</span><span>) </span><span># 使用str()函数将整数123转换为字符串"123"。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>动态类型</strong>: 变量的类型在运行时根据赋值自动确定。</li>
<li><strong>强类型</strong>: Python不允许隐式地进行不兼容类型之间的操作（例如，数字和字符串的直接相加），这有助于减少运行时错误。</li>
<li><strong>不可变与可变</strong>: 某些数据类型（如数字、字符串、元组）是不可变的，一旦创建就不能修改。而其他类型（如列表、字典、集合）是可变的，可以在创建后修改其内容。</li>
</ul></section><section><h3>条件语句<a href="#条件语句"><span>#</span></a></h3><p>条件语句是编程中用于根据特定条件执行不同代码块的结构。它们允许程序根据数据的不同状态做出决策，从而实现更复杂的逻辑。Python使用 <code>if</code>、<code>elif</code>（else if 的缩写）和 <code>else</code> 关键字来构建条件语句，并通过缩进来定义代码块的范围。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>18</span><span> </span><span># 定义一个整数变量age，并赋值为18。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># if 语句：检查age是否小于18。</span></div></div><div><div><div>4</div></div><div><span># 如果条件age &lt; 18为True，则执行if代码块内的语句。</span></div></div><div><div><div>5</div></div><div><span>if</span><span><span> age </span><span>&lt;</span><span> </span></span><span>18</span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>print</span><span>(</span><span>"未成年"</span><span>) </span><span># 输出“未成年”。</span></div></div><div><div><div>7</div></div><div><span># elif 语句：当前面的if条件为False时，检查age是否等于18。</span></div></div><div><div><div>8</div></div><div><span># 如果条件age == 18为True，则执行elif代码块内的语句。</span></div></div><div><div><div>9</div></div><div><span>elif</span><span><span> age </span><span>==</span><span> </span></span><span>18</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"刚好成年"</span><span>) </span><span># 输出“刚好成年”。</span></div></div><div><div><div>11</div></div><div><span># else 语句：当前面所有的if和elif条件都为False时，执行else代码块内的语句。</span></div></div><div><div><div>12</div></div><div><span># else语句是可选的，它捕获所有不满足前面条件的情况。</span></div></div><div><div><div>13</div></div><div><span>else</span><span>:</span></div></div><div><div><div>14</div></div><div><span>    </span><span>print</span><span>(</span><span>"已成年"</span><span>) </span><span># 输出“已成年”。</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span># 另一个条件语句的例子：判断一个数字是正数、负数还是零。</span></div></div><div><div><div>17</div></div><div><span><span>number </span><span>=</span><span> </span><span>-</span></span><span>5</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>if</span><span><span> number </span><span>&gt;</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>20</div></div><div><span>    </span><span>print</span><span>(</span><span>"这是一个正数。"</span><span>)</span></div></div><div><div><div>21</div></div><div><span>elif</span><span><span> number </span><span>&lt;</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>22</div></div><div><span>    </span><span>print</span><span>(</span><span>"这是一个负数。"</span><span>)</span></div></div><div><div><div>23</div></div><div><span>else</span><span>:</span></div></div><div><div><div>24</div></div><div><span>    </span><span>print</span><span>(</span><span>"这是零。"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span># 嵌套条件语句：在一个条件块内部包含另一个条件语句。</span></div></div><div><div><div>27</div></div><div><span><span>score </span><span>=</span><span> </span></span><span>85</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>if</span><span><span> score </span><span>&gt;=</span><span> </span></span><span>60</span><span>:</span></div></div><div><div><div>30</div></div><div><span>    </span><span>if</span><span><span> score </span><span>&gt;=</span><span> </span></span><span>90</span><span>:</span></div></div><div><div><div>31</div></div><div><span>        </span><span>print</span><span>(</span><span>"优秀"</span><span>)</span></div></div><div><div><div>32</div></div><div><span>    </span><span>elif</span><span><span> score </span><span>&gt;=</span><span> </span></span><span>80</span><span>:</span></div></div><div><div><div>33</div></div><div><span>        </span><span>print</span><span>(</span><span>"良好"</span><span>)</span></div></div><div><div><div>34</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>35</div></div><div><span>        </span><span>print</span><span>(</span><span>"及格"</span><span>)</span></div></div><div><div><div>36</div></div><div><span>else</span><span>:</span></div></div><div><div><div>37</div></div><div><span>    </span><span>print</span><span>(</span><span>"不及格"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>条件表达式</strong>: <code>if</code>、<code>elif</code> 后面的表达式，其结果必须是布尔值（<code>True</code> 或 <code>False</code>）。</li>
<li><strong>缩进</strong>: Python使用缩进来表示代码块的层次结构。属于同一条件块的语句必须有相同的缩进。</li>
<li><strong>逻辑顺序</strong>: <code>if</code>、<code>elif</code>、<code>else</code> 语句按顺序执行，一旦某个条件满足并执行了其对应的代码块，整个条件语句就结束了，后续的 <code>elif</code> 和 <code>else</code> 块将不会被检查或执行。</li>
<li><strong>多重条件</strong>: 可以使用 <code>and</code>、<code>or</code>、<code>not</code> 等逻辑运算符组合多个条件，创建更复杂的判断逻辑。</li>
</ul></section><section><h3>循环语句<a href="#循环语句"><span>#</span></a></h3><p>循环语句是编程中用于重复执行特定代码块的结构。当需要多次执行相同的操作，或者遍历数据集合时，循环就显得非常有用。Python主要提供两种类型的循环：<code>for</code> 循环和 <code>while</code> 循环。</p><section><h4><code>for</code> 循环<a href="#for-循环"><span>#</span></a></h4><p><code>for</code> 循环通常用于遍历序列（如列表、元组、字符串）或其他可迭代对象中的元素，或者执行固定次数的循环。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># for循环示例1：遍历数字序列</span></div></div><div><div><div>2</div></div><div><span># range(5) 生成一个从0开始（包含）到5结束（不包含）的整数序列：0, 1, 2, 3, 4。</span></div></div><div><div><div>3</div></div><div><span># 循环将依次把这些数字赋值给变量i，并执行循环体。</span></div></div><div><div><div>4</div></div><div><span>print</span><span>(</span><span>"--- for 循环示例1 ---"</span><span>)</span></div></div><div><div><div>5</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):  </span><span># 循环5次，i从0到4</span></div></div><div><div><div>6</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"当前数字: </span><span>{</span><span>i</span><span>}</span><span>"</span><span>) </span><span># 每次循环打印当前i的值。</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># for循环示例2：遍历列表</span></div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- for 循环示例2 ---"</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>fruits </span><span>=</span><span> [</span></span><span>"苹果"</span><span>, </span><span>"香蕉"</span><span>, </span><span>"橙子"</span><span>]</span></div></div><div><div><div>11</div></div><div><span>for</span><span> fruit </span><span>in</span><span> fruits:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"我喜欢吃: </span><span>{</span><span>fruit</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># for循环示例3：遍历字符串</span></div></div><div><div><div>15</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- for 循环示例3 ---"</span><span>)</span></div></div><div><div><div>16</div></div><div><span><span>word </span><span>=</span><span> </span></span><span>"Python"</span></div></div><div><div><div>17</div></div><div><span>for</span><span> char </span><span>in</span><span> word:</span></div></div><div><div><div>18</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"字符: </span><span>{</span><span>char</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span># for循环示例4：结合enumerate获取索引和值</span></div></div><div><div><div>21</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- for 循环示例4 ---"</span><span>)</span></div></div><div><div><div>22</div></div><div><span>for</span><span> index, fruit </span><span>in</span><span> </span><span>enumerate</span><span>(fruits):</span></div></div><div><div><div>23</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"索引 </span><span>{</span><span>index</span><span>}</span><span>: </span><span>{</span><span>fruit</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4><code>while</code> 循环<a href="#while-循环"><span>#</span></a></h4><p><code>while</code> 循环用于在给定条件为 <code>True</code> 时重复执行代码块。只要条件保持为 <code>True</code>，循环就会一直进行。因此，在使用 <code>while</code> 循环时，务必确保循环条件最终会变为 <code>False</code>，否则会导致无限循环。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># while循环示例1：计数器</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- while 循环示例1 ---"</span><span>)</span></div></div><div><div><div>3</div></div><div><span><span>count </span><span>=</span><span> </span></span><span>0</span><span> </span><span># 初始化计数器变量。</span></div></div><div><div><div>4</div></div><div><span>while</span><span><span> count </span><span>&lt;</span><span> </span></span><span>5</span><span>: </span><span># 只要count小于5，循环就继续。</span></div></div><div><div><div>5</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"当前计数: </span><span>{</span><span>count</span><span>}</span><span>"</span><span>) </span><span># 打印当前计数。</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>count </span><span>+=</span><span> </span><span>1</span><span> </span><span># 每次循环结束后，将count加1，确保循环最终会停止。</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># while循环示例2：用户输入直到满足条件</span></div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- while 循环示例2 ---"</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>password </span><span>=</span><span> </span></span><span>""</span></div></div><div><div><div>11</div></div><div><span>while</span><span><span> password </span><span>!=</span><span> </span></span><span>"secret"</span><span>:</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>password </span><span>=</span><span> </span><span>input</span><span>(</span><span>"请输入密码: "</span><span>)</span></div></div><div><div><div>13</div></div><div><span>    </span><span>if</span><span><span> password </span><span>==</span><span> </span></span><span>"secret"</span><span>:</span></div></div><div><div><div>14</div></div><div><span>        </span><span>print</span><span>(</span><span>"密码正确，欢迎！"</span><span>)</span></div></div><div><div><div>15</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>16</div></div><div><span>        </span><span>print</span><span>(</span><span>"密码错误，请重试。"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>循环控制语句：</strong></p><ul>
<li><code>break</code>: 立即终止当前循环，跳出循环体，执行循环后面的代码。</li>
<li><code>continue</code>: 终止当前循环的本次迭代，跳过循环体中 <code>continue</code> 后面剩余的代码，直接进入下一次迭代。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># break 和 continue 示例</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- break 和 continue 示例 ---"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>10</span><span>):</span></div></div><div><div><div>4</div></div><div><span>    </span><span>if</span><span><span> i </span><span>==</span><span> </span></span><span>3</span><span>:</span></div></div><div><div><div>5</div></div><div><span>        </span><span>print</span><span>(</span><span>"遇到3，跳过本次循环的剩余部分 (continue)"</span><span>)</span></div></div><div><div><div>6</div></div><div><span>        </span><span>continue</span><span> </span><span># 当i为3时，跳过下面的print，直接进入下一次循环。</span></div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span><span> i </span><span>==</span><span> </span></span><span>7</span><span>:</span></div></div><div><div><div>8</div></div><div><span>        </span><span>print</span><span>(</span><span>"遇到7，终止整个循环 (break)"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>        </span><span>break</span><span> </span><span># 当i为7时，立即终止整个for循环。</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"当前数字: </span><span>{</span><span>i</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>核心概念：</strong></p><ul>
<li><strong>迭代</strong>: <code>for</code> 循环通过迭代可迭代对象中的每个元素来工作。</li>
<li><strong>条件</strong>: <code>while</code> 循环依赖于一个布尔条件，只要条件为真就持续执行。</li>
<li><strong>无限循环</strong>: 如果 <code>while</code> 循环的条件永远为 <code>True</code>，就会导致无限循环，程序将无法终止。务必确保循环条件在某个时刻会变为 <code>False</code>。</li>
<li><strong>循环嵌套</strong>: 循环可以嵌套，即一个循环内部包含另一个循环，这在处理多维数据结构时非常常见。</li>
</ul></section></section><section><h3>函数<a href="#函数"><span>#</span></a></h3><p>函数是组织好的、可重复使用的、用于执行特定任务的代码块。它们允许你将程序分解成更小、更易于管理的部分，从而提高代码的模块化、可读性和复用性。在Python中，使用 <code>def</code> 关键字来定义函数。</p><section><h4>定义函数<a href="#定义函数"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 定义函数 (Function Definition)</span></div></div><div><div><div>2</div></div><div><span># def 关键字用于定义函数，后面跟着函数名和括号()，括号内可以包含参数。</span></div></div><div><div><div>3</div></div><div><span># 冒号(:)表示函数头的结束，函数体通过缩进来定义。</span></div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>calculate_area</span><span>(</span><span>length</span><span>,</span><span><span> </span><span>width</span></span><span>): </span><span># 定义一个名为calculate_area的函数，它接受两个参数：length和width。</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""计算矩形面积的函数"""</span><span> </span><span># 这是一个文档字符串(docstring)，用于描述函数的功能。良好的文档字符串是代码可读性的关键。</span></div></div><div><div><div>6</div></div><div><span>    </span><span># return 语句用于从函数中返回值。如果函数没有明确的return语句，它将默认返回None。</span></div></div><div><div><div>7</div></div><div><span>    </span><span>return</span><span><span> length </span><span>*</span><span> width </span></span><span># 函数体：计算length和width的乘积并返回结果。</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 另一个函数定义示例：不带参数，不返回值（隐式返回None）</span></div></div><div><div><div>10</div></div><div><span>def</span><span> </span><span>say_hello</span><span>():</span></div></div><div><div><div>11</div></div><div><span>    </span><span>print</span><span>(</span><span>"Hello, Python!"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>调用函数<a href="#调用函数"><span>#</span></a></h4><p>定义函数后，可以通过函数名后跟括号()来调用它。如果函数定义了参数，调用时需要提供相应的值。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 调用函数 (Function Call)</span></div></div><div><div><div>2</div></div><div><span># 调用calculate_area函数，并传入参数5和3。</span></div></div><div><div><div>3</div></div><div><span><span>area </span><span>=</span><span> </span><span>calculate_area</span><span>(</span></span><span>5</span><span>, </span><span>3</span><span>) </span><span># 函数执行后，返回的结果(15)会被赋值给变量area。</span></div></div><div><div><div>4</div></div><div><span>print</span><span>(</span><span>f</span><span>"面积是：</span><span>{</span><span>area</span><span>}</span><span>"</span><span>) </span><span># 输出计算出的面积。</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 调用不带参数的函数</span></div></div><div><div><div>7</div></div><div><span><span>say_hello</span><span>() </span></span><span># 执行say_hello函数，它会打印"Hello, Python!"。</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>函数参数<a href="#函数参数"><span>#</span></a></h4><p>Python函数支持多种类型的参数，使得函数的使用更加灵活。</p><ol>
<li><strong>位置参数 (Positional Arguments)</strong>: 调用函数时，参数的顺序必须与函数定义时的顺序一致。</li>
<li><strong>关键字参数 (Keyword Arguments)</strong>: 调用函数时，可以通过 <code>参数名=值</code> 的形式指定参数，这样参数的顺序就不重要了。</li>
<li><strong>默认参数 (Default Arguments)</strong>: 在函数定义时为参数指定一个默认值。如果调用函数时没有为该参数提供值，则使用默认值。</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 带默认参数的函数 (Functions with Default Arguments)</span></div></div><div><div><div>2</div></div><div><span># 定义一个greet函数，name参数有一个默认值"访客"。</span></div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>greet</span><span>(</span><span>name</span><span>=</span><span>"访客"</span><span>,</span><span><span> </span><span>greeting</span></span><span>=</span><span>"你好"</span><span>): </span><span># name和greeting都是带默认值的参数。</span></div></div><div><div><div>4</div></div><div><span>    </span><span>return</span><span> </span><span>f</span><span>"</span><span>{</span><span>greeting</span><span>}</span><span>，</span><span>{</span><span>name</span><span>}</span><span>！"</span><span> </span><span># 返回一个格式化的问候语。</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 调用greet函数：</span></div></div><div><div><div>7</div></div><div><span>print</span><span><span>(</span><span>greet</span><span>()) </span></span><span># 不传入任何参数，使用所有默认值。输出: "你好，访客！"</span></div></div><div><div><div>8</div></div><div><span>print</span><span><span>(</span><span>greet</span><span>(</span></span><span>"小明"</span><span>)) </span><span># 传入name参数，使用默认的greeting。输出: "你好，小明！"</span></div></div><div><div><div>9</div></div><div><span>print</span><span><span>(</span><span>greet</span><span>(</span></span><span>name</span><span>=</span><span>"小红"</span><span>, </span><span>greeting</span><span>=</span><span>"早上好"</span><span>)) </span><span># 使用关键字参数，顺序可以颠倒。输出: "早上好，小红！"</span></div></div><div><div><div>10</div></div><div><span>print</span><span><span>(</span><span>greet</span><span>(</span></span><span>greeting</span><span>=</span><span>"晚上好"</span><span>, </span><span>name</span><span>=</span><span>"李华"</span><span>)) </span><span># 关键字参数的顺序不影响。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li><strong>可变参数 (<code>*args</code> 和 <code>**kwargs</code>)</strong>: 允许函数接受不定数量的位置参数和关键字参数。
<ul>
<li><code>*args</code> (arguments): 将所有额外的位置参数收集到一个元组中。</li>
<li><code>**kwargs</code> (keyword arguments): 将所有额外的关键字参数收集到一个字典中。</li>
</ul>
</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>collect_info</span><span>(</span><span>name</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"姓名: </span><span>{</span><span>name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"其他位置信息: </span><span>{</span><span>args</span><span>}</span><span>"</span><span>) </span><span># args是一个元组</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"其他关键字信息: </span><span>{</span><span>kwargs</span><span>}</span><span>"</span><span>) </span><span># kwargs是一个字典</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span><span>collect_info</span><span>(</span></span><span>"张三"</span><span>, </span><span>25</span><span>, </span><span>"男"</span><span>, </span><span>city</span><span>=</span><span>"北京"</span><span>, </span><span>job</span><span>=</span><span>"工程师"</span><span>)</span></div></div><div><div><div>7</div></div><div><span># 输出：</span></div></div><div><div><div>8</div></div><div><span># 姓名: 张三</span></div></div><div><div><div>9</div></div><div><span># 其他位置信息: (25, '男')</span></div></div><div><div><div>10</div></div><div><span># 其他关键字信息: {'city': '北京', 'job': '工程师'}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>核心概念：</strong></p><ul>
<li><strong>DRY (Don’t Repeat Yourself)</strong>: 函数通过封装可重用逻辑来遵循这一原则。</li>
<li><strong>参数与返回值</strong>: 函数通过参数接收输入，通过 <code>return</code> 语句提供输出。</li>
<li><strong>作用域</strong>: 函数内部定义的变量（局部变量）只在该函数内部可见，而函数外部的变量（全局变量）可以在函数内部访问（但不建议直接修改，除非明确声明）。</li>
<li><strong>文档字符串</strong>: 使用三引号字符串在函数定义下方提供函数说明，可以通过 <code>help(function_name)</code> 或 <code>function_name.__doc__</code> 查看。</li>
</ul></section></section><section><h3>列表、字典、集合、元组<a href="#列表字典集合元组"><span>#</span></a></h3><p>Python提供了多种内置的数据结构，它们是组织和存储数据的方式，每种数据结构都有其独特的特性和适用场景。理解这些数据结构对于高效地编写Python代码至关重要。</p><section><h4>列表 (List)<a href="#列表-list"><span>#</span></a></h4><p>列表是Python中最常用的数据结构之一，它是一个有序的、可变的（mutable）集合，可以包含任意类型的对象。列表用方括号 <code>[]</code> 表示。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 列表（List）：有序、可变、可包含重复元素</span></div></div><div><div><div>2</div></div><div><span><span>fruits </span><span>=</span><span> [</span></span><span>"苹果"</span><span>, </span><span>"香蕉"</span><span>, </span><span>"橙子"</span><span>] </span><span># 创建一个包含三个字符串的列表。</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(</span><span>f</span><span>"原始列表: </span><span>{</span><span>fruits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>fruits.</span><span>append</span><span>(</span></span><span>"葡萄"</span><span>)  </span><span># 使用append()方法在列表末尾添加一个元素。</span></div></div><div><div><div>6</div></div><div><span>print</span><span>(</span><span>f</span><span>"添加元素后: </span><span>{</span><span>fruits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>fruits.</span><span>insert</span><span>(</span></span><span>1</span><span>, </span><span>"草莓"</span><span>) </span><span># 使用insert()方法在指定索引位置插入元素。</span></div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>f</span><span>"插入元素后: </span><span>{</span><span>fruits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span><span>fruits.</span><span>remove</span><span>(</span></span><span>"香蕉"</span><span>) </span><span># 使用remove()方法移除指定值的第一个匹配项。</span></div></div><div><div><div>12</div></div><div><span>print</span><span>(</span><span>f</span><span>"移除元素后: </span><span>{</span><span>fruits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>popped_fruit </span><span>=</span><span> fruits.</span><span>pop</span><span>() </span></span><span># 使用pop()方法移除并返回列表末尾的元素（默认）。</span></div></div><div><div><div>15</div></div><div><span>print</span><span>(</span><span>f</span><span>"弹出元素: </span><span>{</span><span>popped_fruit</span><span>}</span><span>, 列表变为: </span><span>{</span><span>fruits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 列表索引和切片</span></div></div><div><div><div>18</div></div><div><span>print</span><span>(</span><span>f</span><span>"第一个元素: </span><span>{</span><span>fruits[</span><span>0</span><span>]</span><span>}</span><span>"</span><span>) </span><span># 访问列表的第一个元素（索引从0开始）。</span></div></div><div><div><div>19</div></div><div><span>print</span><span>(</span><span>f</span><span>"从索引1到2的元素: </span><span>{</span><span>fruits[</span><span>1</span><span>:</span><span>3</span><span>]</span><span>}</span><span>"</span><span>) </span><span># 切片操作，获取子列表。</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span># 列表可以包含不同类型的数据</span></div></div><div><div><div>22</div></div><div><span><span>mixed_list </span><span>=</span><span> [</span></span><span>1</span><span>, </span><span>"hello"</span><span>, </span><span>3.14</span><span>, </span><span>True</span><span>]</span></div></div><div><div><div>23</div></div><div><span>print</span><span>(</span><span>f</span><span>"混合类型列表: </span><span>{</span><span>mixed_list</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>字典 (Dictionary)<a href="#字典-dictionary"><span>#</span></a></h4><p>字典是无序的、可变的键值对（key-value pairs）集合。每个键（key）必须是唯一的，且不可变（如字符串、数字、元组），值（value）可以是任意类型。字典用花括号 <code>{}</code> 表示。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 字典（Dictionary）：无序、可变、键值对存储</span></div></div><div><div><div>2</div></div><div><span><span>person </span><span>=</span><span> { </span></span><span># 创建一个字典，包含姓名、年龄和城市信息。</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"name"</span><span>: </span><span>"小明"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"age"</span><span>: </span><span>18</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"city"</span><span>: </span><span>"北京"</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div><div><div><div>7</div></div><div><span>print</span><span>(</span><span>f</span><span>"原始字典: </span><span>{</span><span>person</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>f</span><span>"姓名: </span><span>{</span><span>person[</span><span>"name"</span><span>]</span><span>}</span><span>"</span><span>) </span><span># 通过键访问字典中的值。</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>person[</span><span>"age"</span><span><span>] </span><span>=</span><span> </span></span><span>19</span><span> </span><span># 修改字典中键对应的值。</span></div></div><div><div><div>12</div></div><div><span>print</span><span>(</span><span>f</span><span>"修改年龄后: </span><span>{</span><span>person</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>person[</span><span>"gender"</span><span><span>] </span><span>=</span><span> </span></span><span>"男"</span><span> </span><span># 添加新的键值对。</span></div></div><div><div><div>15</div></div><div><span>print</span><span>(</span><span>f</span><span>"添加性别后: </span><span>{</span><span>person</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>del</span><span> person[</span><span>"city"</span><span>] </span><span># 使用del关键字删除键值对。</span></div></div><div><div><div>18</div></div><div><span>print</span><span>(</span><span>f</span><span>"删除城市后: </span><span>{</span><span>person</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span># 遍历字典</span></div></div><div><div><div>21</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>遍历字典的键:"</span><span>)</span></div></div><div><div><div>22</div></div><div><span>for</span><span> key </span><span>in</span><span><span> person.</span><span>keys</span><span>(): </span></span><span># 获取所有键。</span></div></div><div><div><div>23</div></div><div><span>    </span><span>print</span><span>(key)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>遍历字典的值:"</span><span>)</span></div></div><div><div><div>26</div></div><div><span>for</span><span> value </span><span>in</span><span><span> person.</span><span>values</span><span>(): </span></span><span># 获取所有值。</span></div></div><div><div><div>27</div></div><div><span>    </span><span>print</span><span>(value)</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>遍历字典的键值对:"</span><span>)</span></div></div><div><div><div>30</div></div><div><span>for</span><span> key, value </span><span>in</span><span><span> person.</span><span>items</span><span>(): </span></span><span># 获取所有键值对。</span></div></div><div><div><div>31</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>key</span><span>}</span><span>: </span><span>{</span><span>value</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>集合 (Set)<a href="#集合-set"><span>#</span></a></h4><p>集合是无序的、可变的、不包含重复元素的集合。主要用于成员测试和消除重复元素。集合用花括号 <code>{}</code> 表示，但创建空集合必须使用 <code>set()</code>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 集合（Set）：无序、可变、元素唯一</span></div></div><div><div><div>2</div></div><div><span><span>numbers </span><span>=</span><span> {</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>, </span><span>3</span><span>, </span><span>4</span><span>}  </span><span># 创建一个集合，重复的3会被自动去除。</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(</span><span>f</span><span>"原始集合: </span><span>{</span><span>numbers</span><span>}</span><span>"</span><span>) </span><span># 结果是{1, 2, 3, 4}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>numbers.</span><span>add</span><span>(</span></span><span>5</span><span>) </span><span># 添加元素。</span></div></div><div><div><div>6</div></div><div><span>print</span><span>(</span><span>f</span><span>"添加元素后: </span><span>{</span><span>numbers</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>numbers.</span><span>remove</span><span>(</span></span><span>1</span><span>) </span><span># 移除元素。</span></div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>f</span><span>"移除元素后: </span><span>{</span><span>numbers</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 集合操作：并集、交集、差集</span></div></div><div><div><div>12</div></div><div><span><span>set_a </span><span>=</span><span> {</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>}</span></div></div><div><div><div>13</div></div><div><span><span>set_b </span><span>=</span><span> {</span></span><span>3</span><span>, </span><span>4</span><span>, </span><span>5</span><span>}</span></div></div><div><div><div>14</div></div><div><span>print</span><span>(</span><span>f</span><span>"并集: </span><span>{</span><span><span>set_a.</span><span>union</span><span>(set_b)</span></span><span>}</span><span>"</span><span>) </span><span># 或 set_a | set_b</span></div></div><div><div><div>15</div></div><div><span>print</span><span>(</span><span>f</span><span>"交集: </span><span>{</span><span><span>set_a.</span><span>intersection</span><span>(set_b)</span></span><span>}</span><span>"</span><span>) </span><span># 或 set_a &amp; set_b</span></div></div><div><div><div>16</div></div><div><span>print</span><span>(</span><span>f</span><span>"差集 (A-B): </span><span>{</span><span><span>set_a.</span><span>difference</span><span>(set_b)</span></span><span>}</span><span>"</span><span>) </span><span># 或 set_a - set_b</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>元组 (Tuple)<a href="#元组-tuple"><span>#</span></a></h4><p>元组是Python中另一种有序的集合，但与列表不同的是，元组是不可变的（immutable）。一旦创建，元组中的元素就不能被修改、添加或删除。元组用圆括号 <code>()</code> 表示。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 元组（Tuple）：有序、不可变、可包含重复元素</span></div></div><div><div><div>2</div></div><div><span><span>coordinates </span><span>=</span><span> (</span></span><span>10</span><span>, </span><span>20</span><span>) </span><span># 创建一个包含两个数字的元组。</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(</span><span>f</span><span>"原始元组: </span><span>{</span><span>coordinates</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 尝试修改元组会报错</span></div></div><div><div><div>6</div></div><div><span># coordinates[0] = 15 # 这会引发TypeError</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 元组的解包</span></div></div><div><div><div>9</div></div><div><span><span>x, y </span><span>=</span><span> coordinates </span></span><span># 将元组的元素分别赋值给x和y。</span></div></div><div><div><div>10</div></div><div><span>print</span><span>(</span><span>f</span><span>"解包后的x: </span><span>{</span><span>x</span><span>}</span><span>, y: </span><span>{</span><span>y</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 包含单个元素的元组需要逗号</span></div></div><div><div><div>13</div></div><div><span><span>single_element_tuple </span><span>=</span><span> (</span></span><span>1</span><span>,) </span><span># 必须有逗号，否则会被认为是普通表达式。</span></div></div><div><div><div>14</div></div><div><span>print</span><span>(</span><span>f</span><span>"单元素元组: </span><span>{</span><span>single_element_tuple</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span># 元组常用于函数返回多个值，或作为字典的键（因为其不可变性）。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念总结：</strong></p><ul>
<li><strong>可变性 (Mutability)</strong>: 列表、字典、集合是可变的，意味着它们的内容在创建后可以被修改。元组是不可变的，一旦创建就不能改变其内容。</li>
<li><strong>有序性 (Order)</strong>: 列表和元组是序的，元素有固定的排列顺序，可以通过索引访问。字典在Python 3.7+版本中保持插入顺序，但传统上被认为是无序的。集合是无序的。</li>
<li><strong>元素唯一性</strong>: 集合中的元素必须是唯一的，重复元素会被自动忽略。列表、字典和元组可以包含重复元素。</li>
<li><strong>适用场景</strong>:
<ul>
<li><strong>列表</strong>: 适用于需要频繁添加、删除或修改元素的有序集合。</li>
<li><strong>字典</strong>: 适用于通过键快速查找、存储和组织数据，例如表示记录或配置信息。</li>
<li><strong>集合</strong>: 适用于需要快速成员测试、去除重复元素或执行数学集合操作（如并集、交集）的场景。</li>
<li><strong>元组</strong>: 适用于存储固定不变的数据集合，例如坐标、函数返回的多个值，或作为字典的键（因为其不可变性）。</li>
</ul>
</li>
</ul></section></section></section><section><h2>进阶<a href="#进阶"><span>#</span></a></h2><section><h3>文件操作<a href="#文件操作"><span>#</span></a></h3><p>文件操作是程序与外部数据交互的重要方式，允许程序读取现有文件中的数据或将数据写入新文件或现有文件。Python提供了简洁直观的内置函数和 <code>with</code> 语句来处理文件操作，确保文件在使用后能够被正确关闭，即使发生错误也不例外。</p><section><h4>文件操作概述<a href="#文件操作概述"><span>#</span></a></h4><p>文件操作主要包括创建、读取、写入、追加和删除文件等操作。Python通过内置的 <code>open()</code> 函数和文件对象提供这些功能。文件操作通常涉及以下关键概念：</p><ul>
<li><strong>文件路径</strong>：可以是相对路径（相对于当前工作目录）或绝对路径（从根目录开始的完整路径）。</li>
<li><strong>文件模式</strong>：决定文件是用于读取、写入还是追加，以及是文本模式还是二进制模式。</li>
<li><strong>编码</strong>：文本文件操作时需要指定正确的字符编码（如UTF-8），特别是处理非ASCII字符时。</li>
<li><strong>缓冲区</strong>：文件操作通常使用缓冲区来提高性能，数据不会立即写入磁盘。</li>
<li><strong>文件指针</strong>：表示当前在文件中的位置，读取和写入操作会移动指针位置。</li>
</ul></section><section><h4>打开文件<a href="#打开文件"><span>#</span></a></h4><p>使用 <code>open()</code> 函数来打开文件。它至少需要一个参数：文件路径。常用的模式有：</p><ul>
<li><code>'r'</code> (read): 读取模式（默认）。文件必须存在。</li>
<li><code>'w'</code> (write): 写入模式。如果文件存在，则清空文件内容；如果文件不存在，则创建新文件。</li>
<li><code>'a'</code> (append): 追加模式。如果文件存在，则在文件末尾添加内容；如果文件不存在，则创建新文件。</li>
<li><code>'x'</code> (exclusive creation): 排他创建模式。如果文件已存在，则操作失败并引发 <code>FileExistsError</code>。</li>
<li><code>'b'</code> (binary): 二进制模式。用于处理非文本文件（如图片、音频）。</li>
<li><code>'t'</code> (text): 文本模式（默认）。用于处理文本文件。</li>
</ul><p><strong>代码示例：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 安全打开文件的最佳实践</span></div></div><div><div><div>2</div></div><div><span>try</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span># 以读取模式打开文本文件，显式指定UTF-8编码</span></div></div><div><div><div>4</div></div><div><span>    </span><span>with</span><span> </span><span>open</span><span>(</span><span>'data.txt'</span><span>, </span><span>'r'</span><span>, </span><span>encoding</span><span>=</span><span>'utf-8'</span><span>) </span><span>as</span><span> </span><span>file</span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>content </span><span>=</span><span> </span><span>file</span><span><span>.</span><span>read</span><span>()</span></span></div></div><div><div><div>6</div></div><div><span>        </span><span>print</span><span>(content)</span></div></div><div><div><div>7</div></div><div><span>except</span><span> </span><span>FileNotFoundError</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>print</span><span>(</span><span>"文件未找到，请检查路径是否正确"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>except</span><span> </span><span>UnicodeDecodeError</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"文件编码错误，请尝试其他编码方式"</span><span>)</span></div></div><div><div><div>11</div></div><div><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"打开文件时发生错误: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>核心概念：</strong></p><ul>
<li><strong>上下文管理器 (<code>with</code> 语句)</strong>: 确保文件在使用后自动关闭，即使发生异常也不例外。</li>
<li><strong>文件模式组合</strong>: 可以组合使用模式，如 <code>'rb'</code> 表示以二进制模式读取，<code>'w+'</code> 表示读写模式。</li>
<li><strong>编码问题</strong>: 处理文本文件时，指定正确的编码至关重要，否则可能导致乱码或解码错误。</li>
<li><strong>异常处理</strong>: 文件操作可能引发多种异常（如 <code>FileNotFoundError</code>, <code>PermissionError</code> 等），应妥善处理。</li>
</ul></section><section><h4>写入文件<a href="#写入文件"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 写入文件 (Writing to a File)</span></div></div><div><div><div>2</div></div><div><span># 使用 'w' 模式打开文件，如果文件不存在则创建，如果存在则清空内容。</span></div></div><div><div><div>3</div></div><div><span># encoding='utf-8' 指定了文件的编码格式，这对于处理包含非ASCII字符（如中文）的文本非常重要。</span></div></div><div><div><div>4</div></div><div><span># 'with' 语句确保文件在操作完成后自动关闭，即使发生异常。</span></div></div><div><div><div>5</div></div><div><span><span>file_path_write </span><span>=</span><span> </span></span><span>"example_write.txt"</span></div></div><div><div><div>6</div></div><div><span>with</span><span> </span><span>open</span><span>(file_path_write, </span><span>"w"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>7</div></div><div><span>    </span><span># write() 方法将字符串写入文件，不会自动添加换行符</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>"这是一个示例文件。</span><span>\n</span><span>"</span><span>) </span><span># 写入一行文本到文件。</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>"这是第二行内容。</span><span>\n</span><span>"</span><span>) </span><span># 继续写入第二行。</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span># writelines() 方法可以写入字符串列表，同样不会自动添加换行符</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> [</span><span>"第三行内容</span><span>\n</span><span>"</span><span>, </span><span>"第四行内容</span><span>\n</span><span>"</span><span>]</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>f.</span><span>writelines</span><span>(lines)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"内容已写入到 </span><span>{</span><span>file_path_write</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 文件写入的缓冲区：默认情况下，写入操作会先写入内存缓冲区，而不是立即写入磁盘</span></div></div><div><div><div>18</div></div><div><span># 可以使用 flush() 方法强制将缓冲区内容写入磁盘</span></div></div><div><div><div>19</div></div><div><span># 或者设置 buffering 参数来控制缓冲区大小</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span><span>**</span><span>核心概念：</span><span>**</span></span></div></div><div><div><div>22</div></div><div><span><span>*</span><span> </span><span>**</span><span>写入方法</span><span>**</span><span>:</span></span></div></div><div><div><div>23</div></div><div><span><span>  </span></span><span>-</span><span> </span><span><span>`</span><span>write</span><span>()</span><span>`</span></span><span>: 写入单个字符串</span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>-</span><span> </span><span><span>`</span><span>writelines</span><span>()</span><span>`</span></span><span>: 写入字符串序列</span></div></div><div><div><div>25</div></div><div><span><span>*</span><span> </span><span>**</span><span>换行处理</span><span>**</span><span>: Python不会自动添加换行符，需要显式添加 </span></span><span>`\</span><span>n`</span></div></div><div><div><div>26</div></div><div><span>*</span><span> </span><span>**</span><span>缓冲区</span><span>**</span><span>: 写入操作通常使用缓冲区，可通过 `</span><span><span>flush</span><span>()</span></span><span>` 强制写入或设置 `</span><span>buffering</span><span>` 参数</span></div></div><div><div><div>27</div></div><div><span><span>*</span><span> </span><span>**</span><span>原子写入</span><span>**</span><span>: 对于关键数据，可能需要临时文件</span><span>+</span><span>重命名的方式确保原子性</span></span></div></div><div><div><div>28</div></div><div><span><span>*</span><span> </span><span>**</span><span>性能考虑</span><span>**</span><span>: 大量小写入操作比少量大写入操作性能差</span></span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span># 追加内容到文件 (Appending to a File)</span></div></div><div><div><div>31</div></div><div><span># 使用 'a' 模式打开文件，如果文件存在则在末尾追加内容。</span></div></div><div><div><div>32</div></div><div><span><span>file_path_append </span><span>=</span><span> </span></span><span>"example_append.txt"</span></div></div><div><div><div>33</div></div><div><span>with</span><span> </span><span>open</span><span>(file_path_append, </span><span>"a"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>"这是追加的第一行。</span><span>\n</span><span>"</span><span>)</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>"这是追加的第二行。</span><span>\n</span><span>"</span><span>)</span></div></div><div><div><div>36</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"内容已追加到 </span><span>{</span><span>file_path_append</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>读取文件<a href="#读取文件"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#### 读取文件</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```python</span></div></div><div><div><div>4</div></div><div><span># 读取文件 (Reading from a File)</span></div></div><div><div><div>5</div></div><div><span># 使用 'r' 模式打开文件。</span></div></div><div><div><div>6</div></div><div><span><span>file_path_read </span><span>=</span><span> </span></span><span>"example_write.txt"</span><span> </span><span># 读取之前写入的文件。</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>try</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>with</span><span> </span><span>open</span><span>(file_path_read, </span><span>"r"</span><span>, </span><span>encoding</span><span>=</span><span>"utf-8"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>10</div></div><div><span>        </span><span># read() 方法读取文件的所有内容作为一个字符串。可以指定读取的字符数。</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>content_full </span><span>=</span><span> f.</span><span>read</span><span>()</span></div></div><div><div><div>12</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>从 </span><span>{</span><span>file_path_read</span><span>}</span><span> 读取到的全部内容:</span><span>\n</span><span>{</span><span>content_full</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>        </span><span># seek(0) 将文件指针重置到文件开头，以便重新读取。</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>f.</span><span>seek</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>16</div></div><div><span>        </span><span># readline() 方法读取文件的一行内容，包括换行符。</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>first_line </span><span>=</span><span> f.</span><span>readline</span><span>()</span></div></div><div><div><div>18</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>从 </span><span>{</span><span>file_path_read</span><span>}</span><span> 读取到的第一行:</span><span>\n</span><span>{</span><span><span>first_line.</span><span>strip</span><span>()</span></span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>f.</span><span>seek</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>21</div></div><div><span>        </span><span># readlines() 方法读取所有行并返回一个字符串列表，每行包含换行符。</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>all_lines </span><span>=</span><span> f.</span><span>readlines</span><span>()</span></div></div><div><div><div>23</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>从 </span><span>{</span><span>file_path_read</span><span>}</span><span> 读取到的所有行(列表形式):</span><span>\n</span><span>{</span><span>all_lines</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>f.</span><span>seek</span><span>(</span><span>0</span><span>)</span></div></div><div><div><div>26</div></div><div><span>        </span><span># 也可以逐行读取文件，这是处理大文件的推荐方式。</span></div></div><div><div><div>27</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>\n</span><span>逐行从 </span><span>{</span><span>file_path_read</span><span>}</span><span> 读取到的内容:"</span><span>)</span></div></div><div><div><div>28</div></div><div><span>        </span><span>for</span><span> line </span><span>in</span><span> f: </span><span># 迭代文件对象可以逐行读取，内存效率高。</span></div></div><div><div><div>29</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"行: </span><span>{</span><span><span>line.</span><span>strip</span><span>()</span></span><span>}</span><span>"</span><span>) </span><span># strip() 移除行末的换行符。</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>except</span><span> </span><span>FileNotFoundError</span><span>:</span></div></div><div><div><div>32</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"错误: 文件 </span><span>{</span><span>file_path_read</span><span>}</span><span> 未找到。"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>34</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"读取文件时发生错误: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span># 读取大文件时，逐行读取或分块读取更高效，避免一次性加载所有内容到内存。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>读取方法</strong>:
<ul>
<li><code>read()</code>: 读取整个文件或指定数量的字符。</li>
<li><code>readline()</code>: 读取文件的一行。</li>
<li><code>readlines()</code>: 读取所有行并返回一个列表。</li>
<li><strong>迭代文件对象</strong>: 最推荐的方式，逐行读取，内存效率最高。</li>
</ul>
</li>
<li><strong>文件指针</strong>: <code>seek(offset, whence)</code> 方法用于移动文件指针。
<ul>
<li><code>offset</code>: 偏移量。</li>
<li><code>whence</code>: 0（文件开头，默认）、1（当前位置）、2（文件末尾）。</li>
</ul>
</li>
<li><strong>性能优化</strong>: 对于大文件，应避免一次性读取所有内容到内存，推荐逐行读取或分块读取。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>**核心概念：**</span></div></div><div><div><div>2</div></div><div><span>*   **文件对象**: `open()` 函数返回一个文件对象，通过它进行读写操作。</span></div></div><div><div><div>3</div></div><div><span>*   **`with` 语句**: 推荐使用 `with open(...) as f:` 语法，它被称为上下文管理器。它能确保文件在使用完毕后（无论是正常结束还是发生异常）自动关闭，从而避免资源泄露。</span></div></div><div><div><div>4</div></div><div><span>*   **文件模式**: 根据读写需求选择合适的模式（`'r'`, `'w'`, `'a'`, `'x'` 等）。</span></div></div><div><div><div>5</div></div><div><span>*   **编码**: 处理文本文件时，指定正确的 `encoding` 参数（如 `'utf-8'`）非常重要，以避免乱码问题。</span></div></div><div><div><div>6</div></div><div><span>*   **文件指针**: 读取和写入操作会移动文件内部的指针。`seek()` 方法可以用来改变文件指针的位置。</span></div></div><div><div><div>7</div></div><div><span>*   **缓冲区**: 文件操作通常涉及缓冲区，理解其工作原理有助于优化性能。</span></div></div><div><div><div>8</div></div><div><span>*   **路径操作**: Python的 `os.path` 模块提供了处理文件路径的工具，如判断路径是否存在、获取文件名、目录名等。</span></div></div><div><div><div>9</div></div><div><span>*   **文件系统操作**: `os` 模块还提供了创建、删除目录，重命名文件等功能。</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>### 异常处理</span></div></div><div><div><div>12</div></div><div><span>异常（Exception）是在程序执行期间发生的事件，它会中断程序的正常流程。当Python脚本遇到无法处理的错误时，它会抛出一个异常。异常处理机制允许程序优雅地处理这些错误，而不是突然崩溃。Python使用 `try`、`except`、`else` 和 `finally` 关键字来构建异常处理结构。</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>#### `try-except` 块</span></div></div><div><div><div>15</div></div><div><span>*   `try` 块：包含可能会引发异常的代码。</span></div></div><div><div><div>16</div></div><div><span>*   `except` 块：当 `try` 块中的代码引发特定类型的异常时，`except` 块中的代码会被执行。</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>**代码示例：**</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>```python</span></div></div><div><div><div>21</div></div><div><span># 异常处理示例</span></div></div><div><div><div>22</div></div><div><span>print("--- 异常处理示例 ---")</span></div></div><div><div><div>23</div></div><div><span>try:</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span># 尝试执行可能出错的代码</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>number_str = input("请输入一个数字：") # 从用户获取输入，这是一个字符串。</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>number = int(number_str) # 尝试将字符串转换为整数。如果输入不是有效数字，会引发ValueError。</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>result = 10 / number # 尝试执行除法。如果number为0，会引发ZeroDivisionError。</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>print(f"结果是：{result}") # 如果以上操作都成功，打印结果。</span></div></div><div><div><div>29</div></div><div><span>except ValueError: # 捕获ValueError异常。</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>print("错误：请输入有效的整数！") # 当用户输入非数字字符串时执行。</span></div></div><div><div><div>31</div></div><div><span>except ZeroDivisionError: # 捕获ZeroDivisionError异常。</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>print("错误：不能除以零！") # 当用户输入0时执行。</span></div></div><div><div><div>33</div></div><div><span>except Exception as e: # 捕获所有其他未被特定except捕获的异常。</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>print(f"发生了未知错误: {e}") # 打印错误信息。</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>print("程序继续执行...") # 无论是否发生异常，程序都会继续执行到这里。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4><code>else</code> 块 (可选)<a href="#else-块-可选"><span>#</span></a></h4><ul>
<li><code>else</code> 块：如果 <code>try</code> 块中的代码<strong>没有</strong>引发任何异常，<code>else</code> 块中的代码就会被执行。它通常用于放置那些只有在 <code>try</code> 块成功执行后才需要运行的代码。</li>
</ul><p><strong>代码示例：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># try-except-else 示例</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- try-except-else 示例 ---"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>try</span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>num1 </span><span>=</span><span> </span><span>int</span><span>(</span><span>input</span><span>(</span><span>"请输入第一个数字: "</span><span>))</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>num2 </span><span>=</span><span> </span><span>int</span><span>(</span><span>input</span><span>(</span><span>"请输入第二个数字: "</span><span>))</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>division_result </span><span>=</span><span> num1 </span><span>/</span><span> num2</span></div></div><div><div><div>7</div></div><div><span>except</span><span> </span><span>ValueError</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>print</span><span>(</span><span>"输入无效，请确保输入的是数字。"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>except</span><span> </span><span>ZeroDivisionError</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"除数不能为零。"</span><span>)</span></div></div><div><div><div>11</div></div><div><span>else</span><span>:</span></div></div><div><div><div>12</div></div><div><span>    </span><span># 如果try块没有发生异常，则执行此处的代码</span></div></div><div><div><div>13</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"两个数字相除的结果是: </span><span>{</span><span>division_result</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4><code>finally</code> 块 (可选)<a href="#finally-块-可选"><span>#</span></a></h4><ul>
<li><code>finally</code> 块：无论 <code>try</code> 块中是否发生异常，<code>finally</code> 块中的代码<strong>总是</strong>会被执行。它通常用于执行清理操作，例如关闭文件或释放资源。</li>
</ul><p><strong>代码示例：</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># try-except-finally 示例</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- try-except-finally 示例 ---"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>file</span><span><span> </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>4</div></div><div><span>try</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>file</span><span><span> </span><span>=</span><span> </span></span><span>open</span><span>(</span><span>"non_existent_file.txt"</span><span>, </span><span>"r"</span><span>) </span><span># 尝试打开一个不存在的文件，会引发FileNotFoundError。</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>content </span><span>=</span><span> </span><span>file</span><span><span>.</span><span>read</span><span>()</span></span></div></div><div><div><div>7</div></div><div><span>    </span><span>print</span><span>(content)</span></div></div><div><div><div>8</div></div><div><span>except</span><span> </span><span>FileNotFoundError</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>print</span><span>(</span><span>"文件未找到。"</span><span>)</span></div></div><div><div><div>10</div></div><div><span>finally</span><span>:</span></div></div><div><div><div>11</div></div><div><span>    </span><span># 无论是否发生异常，都会执行这里的代码</span></div></div><div><div><div>12</div></div><div><span>    </span><span>if</span><span> </span><span>file</span><span>:</span></div></div><div><div><div>13</div></div><div><span>        </span><span>file</span><span><span>.</span><span>close</span><span>() </span></span><span># 确保文件被关闭，释放资源。</span></div></div><div><div><div>14</div></div><div><span>        </span><span>print</span><span>(</span><span>"文件已关闭。"</span><span>)</span></div></div><div><div><div>15</div></div><div><span>    </span><span>print</span><span>(</span><span>"程序执行完毕。"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>异常类型</strong>: Python有许多内置的异常类型（如 <code>ValueError</code>, <code>TypeError</code>, <code>FileNotFoundError</code>, <code>IndexError</code> 等），可以根据需要捕获特定的异常。了解常见的内置异常有助于编写更健壮的代码。</li>
<li><strong>异常捕获顺序</strong>: <code>except</code> 块会按顺序匹配异常。更具体的异常（子类）应该放在前面，更通用的异常（父类，如 <code>Exception</code>）应该放在后面，否则通用异常会捕获所有异常，导致具体异常无法被捕获。</li>
<li><strong><code>as</code> 关键字</strong>: 可以使用 <code>as</code> 关键字将捕获到的异常对象赋值给一个变量，以便获取更多关于异常的信息，例如错误消息或堆栈跟踪。</li>
<li><strong><code>raise</code> 语句</strong>: 可以使用 <code>raise</code> 语句手动引发异常。这在自定义错误处理逻辑、验证输入或在特定条件下强制程序中断时非常有用。</li>
<li><strong>资源管理</strong>: <code>finally</code> 块对于确保资源（如文件、网络连接、数据库连接）被正确关闭或释放至关重要，即使在 <code>try</code> 块中发生异常也能保证清理工作。<code>with</code> 语句（上下文管理器）是更推荐的资源管理方式。</li>
<li><strong>自定义异常</strong>: 可以通过继承 <code>Exception</code> 类来创建自定义异常，这有助于提高代码的可读性和可维护性，使错误类型更具业务含义。</li>
<li><strong>异常链</strong>: Python 3 引入了异常链，允许在捕获一个异常后引发另一个异常，同时保留原始异常的信息，有助于调试。</li>
</ul></section></section><section><h3>类和对象<a href="#类和对象"><span>#</span></a></h3><p>面向对象编程（Object-Oriented Programming, OOP）是Python中一种强大的编程范式，它通过“类”和“对象”的概念来组织代码，模拟现实世界中的实体和它们之间的关系。OOP的核心思想是将数据（属性）和操作数据的方法（行为）封装在一起，形成一个独立的单元。</p><section><h4>类 (Class)<a href="#类-class"><span>#</span></a></h4><p>类是创建对象的蓝图或模板。它定义了对象的属性（数据）和方法（函数）。在Python中，使用 <code>class</code> 关键字来定义类。</p><p><strong>代码示例：</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 定义一个名为Student的类</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>Student</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span># 类的属性（通常在__init__方法中定义实例属性）</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>school_name </span><span>=</span><span> </span><span>"某某大学"</span><span> </span><span># 这是一个类属性，所有Student的实例共享。</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span># 构造方法 (Constructor): __init__</span></div></div><div><div><div>7</div></div><div><span>    </span><span># 这是一个特殊的方法，当创建类的新实例（对象）时会自动调用。</span></div></div><div><div><div>8</div></div><div><span>    </span><span># self 参数是必须的，它代表了类的实例本身。</span></div></div><div><div><div>9</div></div><div><span>    </span><span># name 和 age 是实例属性，它们属于每个Student对象独有。</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>,</span><span><span> </span><span>age</span></span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.name </span><span>=</span><span> name </span></span><span># 将传入的name参数赋值给实例的name属性。</span></div></div><div><div><div>12</div></div><div><span>        </span><span>self</span><span><span>.age </span><span>=</span><span> age   </span></span><span># 将传入的age参数赋值给实例的age属性。</span></div></div><div><div><div>13</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"学生 </span><span>{</span><span>self</span><span>.name</span><span>}</span><span> 已创建。"</span><span>)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span># 实例方法 (Instance Method)</span></div></div><div><div><div>16</div></div><div><span>    </span><span># 方法是定义在类中的函数，用于描述对象的行为。它们也必须接受self作为第一个参数。</span></div></div><div><div><div>17</div></div><div><span>    </span><span>def</span><span> </span><span>introduce</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>18</div></div><div><span>        </span><span># 访问实例属性和类属性</span></div></div><div><div><div>19</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"我叫</span><span>{</span><span>self</span><span>.name</span><span>}</span><span>，今年</span><span>{</span><span>self</span><span>.age</span><span>}</span><span>岁，就读于 </span><span>{</span><span>Student.school_name</span><span>}</span><span>。"</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>    </span><span>def</span><span> </span><span>celebrate_birthday</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>22</div></div><div><span>        </span><span>self</span><span><span>.age </span><span>+=</span><span> </span></span><span>1</span><span> </span><span># 修改实例属性</span></div></div><div><div><div>23</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>self</span><span>.name</span><span>}</span><span> 庆祝了生日，现在 </span><span>{</span><span>self</span><span>.age</span><span>}</span><span> 岁了！"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>对象 (Object / Instance)<a href="#对象-object--instance"><span>#</span></a></h4><p>对象是类的具体实例。通过类定义，我们可以创建多个具有相同属性和行为但数据值不同的对象。</p><p><strong>代码示例：</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 创建对象 (Creating Objects / Instances)</span></div></div><div><div><div>2</div></div><div><span># 通过调用类名并传入__init__方法所需的参数来创建Student类的实例。</span></div></div><div><div><div>3</div></div><div><span><span>student1 </span><span>=</span><span> </span><span>Student</span><span>(</span></span><span>"小明"</span><span>, </span><span>18</span><span>) </span><span># 创建第一个Student对象。</span></div></div><div><div><div>4</div></div><div><span><span>student2 </span><span>=</span><span> </span><span>Student</span><span>(</span></span><span>"小红"</span><span>, </span><span>19</span><span>) </span><span># 创建第二个Student对象。</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 访问对象的属性</span></div></div><div><div><div>7</div></div><div><span>print</span><span>(</span><span>f</span><span>"学生1的姓名: </span><span>{</span><span>student1.name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>8</div></div><div><span>print</span><span>(</span><span>f</span><span>"学生2的年龄: </span><span>{</span><span>student2.age</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>9</div></div><div><span>print</span><span>(</span><span>f</span><span>"学生1的学校: </span><span>{</span><span>student1.school_name</span><span>}</span><span>"</span><span>) </span><span># 可以通过实例访问类属性</span></div></div><div><div><div>10</div></div><div><span>print</span><span>(</span><span>f</span><span>"学生2的学校: </span><span>{</span><span>Student.school_name</span><span>}</span><span>"</span><span>) </span><span># 也可以通过类名访问类属性</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 调用对象的方法</span></div></div><div><div><div>13</div></div><div><span>print</span><span><span>(student1.</span><span>introduce</span><span>()) </span></span><span># 调用student1对象的introduce方法。</span></div></div><div><div><div>14</div></div><div><span>print</span><span><span>(student2.</span><span>introduce</span><span>()) </span></span><span># 调用student2对象的introduce方法。</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>student1.</span><span>celebrate_birthday</span><span>() </span></span><span># 调用student1的生日方法</span></div></div><div><div><div>17</div></div><div><span>print</span><span><span>(student1.</span><span>introduce</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>封装 (Encapsulation)</strong>: 将数据（属性）和操作数据的方法（行为）捆绑在一起，形成一个独立的单元（类）。这有助于隐藏内部实现细节，只暴露必要的接口，提高代码的安全性和可维护性。</li>
<li><strong><code>self</code></strong>: 在类的方法中，<code>self</code> 总是指向当前实例（对象）本身。它是访问实例属性和调用实例方法的约定，必须是方法的第一个参数。</li>
<li><strong><code>__init__</code> 方法</strong>: 构造方法，用于初始化新创建的对象的属性。它在对象被创建时自动调用，是类的特殊方法之一。</li>
<li><strong>实例属性 vs. 类属性</strong>:
<ul>
<li><strong>实例属性</strong>: 属于类的每个独立对象，每个对象都有自己的一份副本（如 <code>name</code>, <code>age</code>）。通过 <code>self.attribute_name</code> 访问。</li>
<li><strong>类属性</strong>: 属于类本身，所有类的实例共享同一个副本（如 <code>school_name</code>）。通过 <code>ClassName.attribute_name</code> 或 <code>self.attribute_name</code> 访问。</li>
</ul>
</li>
<li><strong>方法</strong>: 定义在类中的函数，用于描述对象的行为。分为实例方法、类方法和静态方法。
<ul>
<li><strong>实例方法</strong>: 接收 <code>self</code> 作为第一个参数，操作实例属性。</li>
<li><strong>类方法</strong>: 使用 <code>@classmethod</code> 装饰器，接收 <code>cls</code>（类本身）作为第一个参数，操作类属性或创建类实例。</li>
<li><strong>静态方法</strong>: 使用 <code>@staticmethod</code> 装饰器，不接收 <code>self</code> 或 <code>cls</code>，行为独立于实例和类，类似于普通函数，但逻辑上与类相关。</li>
</ul>
</li>
</ul></section></section><section><h3>继承<a href="#继承"><span>#</span></a></h3><p>继承是面向对象编程的另一个核心概念，它允许一个类（子类或派生类）从另一个类（父类、基类或超类）中继承属性和方法。这促进了代码的重用性，并建立了“is-a”关系（例如，“老师是一种人”）。子类可以扩展或修改父类的行为。</p><section><h4>基本继承<a href="#基本继承"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 定义父类 (Parent Class / Base Class)</span></div></div><div><div><div>2</div></div><div><span>class</span><span><span> </span><span>Person</span></span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>,</span><span><span> </span><span>age</span></span><span>):</span></div></div><div><div><div>4</div></div><div><span>        </span><span>self</span><span><span>.name </span><span>=</span><span> name </span></span><span># 实例属性：姓名</span></div></div><div><div><div>5</div></div><div><span>        </span><span>self</span><span><span>.age </span><span>=</span><span> age   </span></span><span># 实例属性：年龄</span></div></div><div><div><div>6</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"Person </span><span>{</span><span>self</span><span>.name</span><span>}</span><span> 已创建。"</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>greet</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span>"""父类方法：打招呼"""</span></div></div><div><div><div>10</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"你好，我是</span><span>{</span><span>self</span><span>.name</span><span>}</span><span>，我今年</span><span>{</span><span>self</span><span>.age</span><span>}</span><span>岁。"</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 定义子类 (Child Class / Derived Class)</span></div></div><div><div><div>13</div></div><div><span># Teacher类继承自Person类，在类名后括号中指定父类。</span></div></div><div><div><div>14</div></div><div><span>class</span><span><span> </span><span>Teacher</span><span>(</span><span>Person</span><span>)</span></span><span>:</span></div></div><div><div><div>15</div></div><div><span>    </span><span># 子类的构造方法</span></div></div><div><div><div>16</div></div><div><span>    </span><span># 当子类有自己的__init__方法时，它会覆盖父类的__init__方法。</span></div></div><div><div><div>17</div></div><div><span>    </span><span># 如果需要调用父类的__init__方法来初始化父类的属性，必须显式调用super().__init__()。</span></div></div><div><div><div>18</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>,</span><span><span> </span><span>age</span></span><span>,</span><span><span> </span><span>subject</span></span><span>):</span></div></div><div><div><div>19</div></div><div><span>        </span><span># 调用父类Person的构造方法，初始化name和age属性。</span></div></div><div><div><div>20</div></div><div><span>        </span><span># super() 函数返回父类的代理对象，允许你调用父类的方法。</span></div></div><div><div><div>21</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(name, age)</span></div></div><div><div><div>22</div></div><div><span>        </span><span>self</span><span><span>.subject </span><span>=</span><span> subject </span></span><span># 子类特有的属性：教授科目。</span></div></div><div><div><div>23</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"Teacher </span><span>{</span><span>self</span><span>.name</span><span>}</span><span> (教授</span><span>{</span><span>self</span><span>.subject</span><span>}</span><span>) 已创建。"</span><span>)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>    </span><span># 子类特有的方法</span></div></div><div><div><div>26</div></div><div><span>    </span><span>def</span><span> </span><span>teach</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>27</div></div><div><span>        </span><span>"""子类特有方法：教学"""</span></div></div><div><div><div>28</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"我叫</span><span>{</span><span>self</span><span>.name</span><span>}</span><span>，我教</span><span>{</span><span>self</span><span>.subject</span><span>}</span><span>。"</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span># 创建子类对象</span></div></div><div><div><div>31</div></div><div><span><span>teacher </span><span>=</span><span> </span><span>Teacher</span><span>(</span></span><span>"张老师"</span><span>, </span><span>35</span><span>, </span><span>"数学"</span><span>)</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span># 调用继承自父类的方法</span></div></div><div><div><div>34</div></div><div><span>print</span><span><span>(teacher.</span><span>greet</span><span>())  </span></span><span># 输出: 你好，我是张老师，我今年35岁。</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span># 调用子类特有的方法</span></div></div><div><div><div>37</div></div><div><span>print</span><span><span>(teacher.</span><span>teach</span><span>())  </span></span><span># 输出: 我叫张老师，我教数学。</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span># 检查对象类型</span></div></div><div><div><div>40</div></div><div><span>print</span><span>(</span><span>f</span><span>"teacher是Person的实例吗？ </span><span>{</span><span>isinstance</span><span>(teacher, Person)</span><span>}</span><span>"</span><span>) </span><span># True</span></div></div><div><div><div>41</div></div><div><span>print</span><span>(</span><span>f</span><span>"teacher是Teacher的实例吗？ </span><span>{</span><span>isinstance</span><span>(teacher, Teacher)</span><span>}</span><span>"</span><span>) </span><span># True</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span># 检查类之间的继承关系</span></div></div><div><div><div>44</div></div><div><span>print</span><span>(</span><span>f</span><span>"Teacher是Person的子类吗？ </span><span>{</span><span>issubclass</span><span>(Teacher, Person)</span><span>}</span><span>"</span><span>) </span><span># True</span></div></div><div><div><div>45</div></div><div><span>print</span><span>(</span><span>f</span><span>"Person是Teacher的子类吗？ </span><span>{</span><span>issubclass</span><span>(Person, Teacher)</span><span>}</span><span>"</span><span>) </span><span># False</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>方法重写 (Method Overriding)<a href="#方法重写-method-overriding"><span>#</span></a></h4><p>子类可以定义与父类中同名的方法。当子类对象调用这个方法时，会执行子类中定义的方法，而不是父类的方法。这被称为方法重写。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>Animal</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>speak</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>"""父类方法：动物发出声音"""</span></div></div><div><div><div>4</div></div><div><span>        </span><span>return</span><span> </span><span>"动物发出声音"</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>Dog</span><span>(</span><span>Animal</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>speak</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span>"""子类重写方法：狗发出声音"""</span></div></div><div><div><div>9</div></div><div><span>        </span><span># 可以在重写的方法中调用父类的方法，以扩展而非完全替换父类行为</span></div></div><div><div><div>10</div></div><div><span>        </span><span># return f"{super().speak()}，然后汪汪叫"</span></div></div><div><div><div>11</div></div><div><span>        </span><span>return</span><span> </span><span>"汪汪叫"</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>class</span><span><span> </span><span>Cat</span><span>(</span><span>Animal</span><span>)</span></span><span>:</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>speak</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>"""子类重写方法：猫发出声音"""</span></div></div><div><div><div>16</div></div><div><span>        </span><span>return</span><span> </span><span>"喵喵叫"</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span><span>animal </span><span>=</span><span> </span><span>Animal</span><span>()</span></span></div></div><div><div><div>19</div></div><div><span><span>dog </span><span>=</span><span> </span><span>Dog</span><span>()</span></span></div></div><div><div><div>20</div></div><div><span><span>cat </span><span>=</span><span> </span><span>Cat</span><span>()</span></span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>print</span><span><span>(animal.</span><span>speak</span><span>()) </span></span><span># 输出: 动物发出声音</span></div></div><div><div><div>23</div></div><div><span>print</span><span><span>(dog.</span><span>speak</span><span>())    </span></span><span># 输出: 汪汪叫 (子类重写了方法)</span></div></div><div><div><div>24</div></div><div><span>print</span><span><span>(cat.</span><span>speak</span><span>())    </span></span><span># 输出: 喵喵叫 (子类重写了方法)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>多重继承 (Multiple Inheritance)<a href="#多重继承-multiple-inheritance"><span>#</span></a></h4><p>Python支持一个类继承自多个父类。当存在多个父类时，Python会按照特定的顺序（MRO - Method Resolution Order）来查找方法和属性。MRO遵循C3线性化算法。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span><span> </span><span>Flyer</span></span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>def</span><span> </span><span>fly</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>3</div></div><div><span>        </span><span>return</span><span> </span><span>"我会飞！"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>Swimmer</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>swim</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>return</span><span> </span><span>"我会游泳！"</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>class</span><span><span> </span><span>Duck</span><span>(</span><span>Flyer</span><span>, </span><span>Swimmer</span><span>)</span></span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>quack</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>return</span><span> </span><span>"嘎嘎！"</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>duck </span><span>=</span><span> </span><span>Duck</span><span>()</span></span></div></div><div><div><div>14</div></div><div><span>print</span><span><span>(duck.</span><span>fly</span><span>())   </span></span><span># 输出: 我会飞！</span></div></div><div><div><div>15</div></div><div><span>print</span><span><span>(duck.</span><span>swim</span><span>())  </span></span><span># 输出: 我会游泳！</span></div></div><div><div><div>16</div></div><div><span>print</span><span><span>(duck.</span><span>quack</span><span>()) </span></span><span># 输出: 嘎嘎！</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># 查看方法解析顺序 (MRO)</span></div></div><div><div><div>19</div></div><div><span>print</span><span>(Duck.</span><span>__mro__</span><span>)</span></div></div><div><div><div>20</div></div><div><span># 输出示例: (&lt;class '__main__.Duck'&gt;, &lt;class '__main__.Flyer'&gt;, &lt;class '__main__.Swimmer'&gt;, &lt;class 'object'&gt;)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>代码重用</strong>: 继承允许子类重用父类的代码和行为，减少代码冗余，提高开发效率。</li>
<li><strong><code>super()</code></strong>: 一个内置函数，用于调用父类（超类）的方法。在子类的 <code>__init__</code> 方法中调用父类的构造方法是其常见用法，也可以在重写方法中调用父类的同名方法来扩展其功能。</li>
<li><strong>多态 (Polymorphism)</strong>: 指不同类的对象对同一消息（方法调用）可以有不同的响应方式。它允许我们编写更通用、更灵活的代码，例如，一个函数可以接受不同类型的对象，只要它们都实现了某个共同的方法。</li>
<li><strong><code>isinstance()</code></strong>: 用于检查一个对象是否是某个类或其子类的实例。返回 <code>True</code> 或 <code>False</code>。</li>
<li><strong><code>issubclass()</code></strong>: 用于检查一个类是否是另一个类的子类。返回 <code>True</code> 或 <code>False</code>。</li>
<li><strong>多重继承</strong>: Python支持一个类继承自多个父类。这使得子类可以结合多个父类的功能。然而，多重继承可能导致方法解析顺序（MRO）的复杂性，需要谨慎使用。</li>
<li><strong>方法解析顺序 (MRO)</strong>: 当使用多重继承时，Python通过MRO来确定方法和属性的查找顺序。Python 3 采用C3线性化算法来计算MRO，可以通过 <code>ClassName.__mro__</code> 或 <code>help(ClassName)</code> 查看。</li>
</ul></section></section><section><h3>模块和包<a href="#模块和包"><span>#</span></a></h3><p>随着Python项目的增长，代码量会越来越大，为了更好地组织和管理代码，Python引入了“模块”和“包”的概念。它们有助于将代码分解成逻辑单元，提高代码的可维护性、可重用性和可读性。</p><section><h4>模块 (Module)<a href="#模块-module"><span>#</span></a></h4><p>模块是一个包含Python定义和语句的文件。文件名就是模块名加上 <code>.py</code> 后缀。模块可以定义函数、类和变量，也可以包含可执行的代码。使用模块可以把相关的代码组织在一起，方便重用。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 导入整个模块</span></div></div><div><div><div>2</div></div><div><span># import math 语句导入Python的内置math模块，该模块提供了许多数学函数和常量。</span></div></div><div><div><div>3</div></div><div><span>import</span><span> math</span></div></div><div><div><div>4</div></div><div><span>print</span><span>(</span><span>f</span><span>"圆周率 (math.pi): </span><span>{</span><span>math.pi</span><span>}</span><span>"</span><span>) </span><span># 通过模块名.属性名的方式访问模块中的常量。</span></div></div><div><div><div>5</div></div><div><span>print</span><span>(</span><span>f</span><span>"16的平方根 (math.sqrt(16)): </span><span>{</span><span><span>math.</span><span>sqrt</span><span>(</span></span><span>16</span><span>)</span><span>}</span><span>"</span><span>) </span><span># 访问模块中的函数。</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 从模块中导入特定函数或变量</span></div></div><div><div><div>8</div></div><div><span># from random import randint 语句只导入random模块中的randint函数，可以直接使用randint而不需要加random前缀。</span></div></div><div><div><div>9</div></div><div><span>from</span><span> random </span><span>import</span><span> randint</span></div></div><div><div><div>10</div></div><div><span><span>random_number </span><span>=</span><span> </span><span>randint</span><span>(</span></span><span>1</span><span>, </span><span>10</span><span>) </span><span># 生成一个1到10之间的随机整数（包含1和10）。</span></div></div><div><div><div>11</div></div><div><span>print</span><span>(</span><span>f</span><span>"随机整数 (randint(1, 10)): </span><span>{</span><span>random_number</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 导入模块并使用别名</span></div></div><div><div><div>14</div></div><div><span># import numpy as np 导入numpy模块并给它一个别名np，方便后续使用。</span></div></div><div><div><div>15</div></div><div><span># import collections as coll</span></div></div><div><div><div>16</div></div><div><span># from datetime import datetime as dt</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># 导入模块中的所有内容（不推荐，可能导致命名冲突）</span></div></div><div><div><div>19</div></div><div><span># from os import *</span></div></div><div><div><div>20</div></div><div><span># print(getcwd()) # 可以直接使用getcwd函数</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>包 (Package)<a href="#包-package"><span>#</span></a></h4><p>包是一种组织Python模块的方式，它是一个包含多个模块和其他子包的目录。包的目录中必须包含一个特殊的 <code>__init__.py</code> 文件（在Python 3.3+版本中，这个文件可以是空的，甚至可以省略，但为了兼容性和明确性，通常还是会保留）。<code>__init__.py</code> 文件告诉Python解释器，该目录应该被视为一个Python包。</p><p><strong>包的结构示例：</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>my_project/</span></div></div><div><div><div>2</div></div><div><span>├── main.py</span></div></div><div><div><div>3</div></div><div><span>└── my_package/</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>├── __init__.py</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>├── module_a.py</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>└── sub_package/</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>├── __init__.py</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>└── module_b.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 假设有上述包结构，并且在my_package/module_a.py中有如下内容：</span></div></div><div><div><div>2</div></div><div><span># def func_a():</span></div></div><div><div><div>3</div></div><div><span>#     return "Hello from module_a!"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 假设在my_package/sub_package/module_b.py中有如下内容：</span></div></div><div><div><div>6</div></div><div><span># def func_b():</span></div></div><div><div><div>7</div></div><div><span>#     return "Hello from module_b!"</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 导入包中的模块</span></div></div><div><div><div>10</div></div><div><span>import</span><span> my_package.module_a  </span><span># 导入my_package包中的module_a模块</span></div></div><div><div><div>11</div></div><div><span>print</span><span><span>(my_package.module_a.</span><span>func_a</span><span>()) </span></span><span># 访问方式：包名.模块名.函数名</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 从包中导入特定模块</span></div></div><div><div><div>14</div></div><div><span>from</span><span> my_package </span><span>import</span><span> module_a</span></div></div><div><div><div>15</div></div><div><span>print</span><span><span>(module_a.</span><span>func_a</span><span>()) </span></span><span># 访问方式：模块名.函数名</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 从包中的模块导入特定函数</span></div></div><div><div><div>18</div></div><div><span>from</span><span> my_package.module_a </span><span>import</span><span> func_a</span></div></div><div><div><div>19</div></div><div><span>print</span><span><span>(</span><span>func_a</span><span>()) </span></span><span># 访问方式：函数名</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span># 导入子包中的模块</span></div></div><div><div><div>22</div></div><div><span>from</span><span> my_package.sub_package </span><span>import</span><span> module_b</span></div></div><div><div><div>23</div></div><div><span>print</span><span><span>(module_b.</span><span>func_b</span><span>()) </span></span><span># 访问方式：模块名.函数名</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span># 使用 __all__ 控制导入行为</span></div></div><div><div><div>26</div></div><div><span># 在 __init__.py 中定义 __all__ = ['module1', 'module2'] 可以控制 from mypackage import * 的行为</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span># 相对导入（在包内部使用）</span></div></div><div><div><div>29</div></div><div><span># from . import module1  # 导入同级模块</span></div></div><div><div><div>30</div></div><div><span># from .. import parent_module  # 导入上级模块</span></div></div><div><div><div>31</div></div><div><span># from .subpackage import module3  # 导入子包模块</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>命名空间</strong>: 模块和包有助于避免命名冲突。每个模块都有自己的命名空间，通过 <code>.</code> 运算符可以访问其中的内容。</li>
<li><strong>可重用性</strong>: 将代码组织成模块和包，可以方便地在不同的项目中重用。</li>
<li><strong>可维护性</strong>: 将大型项目分解为更小的、独立的模块，使得代码更容易理解、测试和维护。</li>
<li><strong><code>__init__.py</code></strong>: 标记一个目录为Python包。它也可以包含包的初始化代码，例如定义 <code>__all__</code> 变量来控制 <code>from package import *</code> 时的导入行为。</li>
<li><strong><code>__all__</code></strong>: 在 <code>__init__.py</code> 中定义，是一个字符串列表，用于控制 <code>from package import *</code> 时导入哪些模块。</li>
<li><strong>相对导入</strong>: 在包内部可以使用相对导入（如 <code>from . import module</code>），使用点号表示当前包和父包。</li>
<li><strong>模块搜索路径</strong>: Python在导入模块时会按照 <code>sys.path</code> 中的路径顺序搜索模块。</li>
<li><strong><code>if __name__ == '__main__'</code></strong>: 用于判断模块是直接运行还是被导入，常用于模块测试代码。</li>
</ul></section></section><section><h3>装饰器<a href="#装饰器"><span>#</span></a></h3><p>装饰器是Python中一个非常强大和常用的功能，它允许你修改或增强函数或类的功能，而无需修改其源代码。本质上，装饰器是一个函数，它接收一个函数作为输入，并返回一个新的函数作为输出。这种设计模式在日志记录、性能测试、事务处理、缓存、权限校验等方面非常有用。</p><section><h4>装饰器的工作原理<a href="#装饰器的工作原理"><span>#</span></a></h4><p>装饰器通过 <code>@decorator_name</code> 语法糖应用在一个函数定义之前。当一个函数被装饰时，实际上发生的是：
<code>decorated_function = decorator_name(original_function)</code>
然后，当你调用 <code>decorated_function()</code> 时，实际上是调用了 <code>decorator_name</code> 返回的新函数。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 定义一个简单的装饰器</span></div></div><div><div><div>2</div></div><div><span># my_decorator 是一个装饰器函数，它接收一个函数 func 作为参数。</span></div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>my_decorator</span><span>(</span><span>func</span><span>):</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>基础装饰器示例</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>:param func: 被装饰的函数</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>:return: 包装后的函数</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>9</div></div><div><span>    </span><span># wrapper 是一个内部函数，它将替代原始函数 func 被调用。</span></div></div><div><div><div>10</div></div><div><span>    </span><span># *args 和 **kwargs 允许 wrapper 函数接受任意数量的位置参数和关键字参数，并将它们传递给原始函数 func。</span></div></div><div><div><div>11</div></div><div><span>    </span><span>def</span><span> </span><span>wrapper</span><span>(*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>12</div></div><div><span>        </span><span>print</span><span>(</span><span>"Something is happening before the function is called."</span><span>) </span><span># 在原始函数执行前打印一条消息。</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>result </span><span>=</span><span> </span><span>func</span><span>(*args, **kwargs) </span><span># 调用原始函数 func，并将其返回值存储在 result 中。</span></div></div><div><div><div>14</div></div><div><span>        </span><span>print</span><span>(</span><span>"Something is happening after the function is called."</span><span>)  </span><span># 在原始函数执行后打印一条消息。</span></div></div><div><div><div>15</div></div><div><span>        </span><span>return</span><span> result </span><span># 返回原始函数的执行结果。</span></div></div><div><div><div>16</div></div><div><span>    </span><span>return</span><span> wrapper </span><span># 装饰器返回 wrapper 函数。</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># 使用 @ 语法糖应用装饰器</span></div></div><div><div><div>19</div></div><div><span># @my_decorator 相当于 say_hello = my_decorator(say_hello)。</span></div></div><div><div><div>20</div></div><div><span># 当 say_hello() 被调用时，实际上是调用了 my_decorator 返回的 wrapper 函数。</span></div></div><div><div><div>21</div></div><div><span>@my_decorator</span></div></div><div><div><div>22</div></div><div><span>def</span><span> </span><span>say_hello</span><span>():</span></div></div><div><div><div>23</div></div><div><span>    </span><span>"""被装饰的函数示例"""</span></div></div><div><div><div>24</div></div><div><span>    </span><span>print</span><span>(</span><span>"Hello!"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span># 调用被装饰的函数</span></div></div><div><div><div>27</div></div><div><span><span>say_hello</span><span>()</span></span></div></div><div><div><div>28</div></div><div><span># 输出：</span></div></div><div><div><div>29</div></div><div><span># Something is happening before the function is called.</span></div></div><div><div><div>30</div></div><div><span># Hello!</span></div></div><div><div><div>31</div></div><div><span># Something is happening after the function is called.</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 带有参数的函数装饰器 --"</span><span>)</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>@my_decorator</span></div></div><div><div><div>36</div></div><div><span>def</span><span> </span><span>greet</span><span>(</span><span>name</span><span>):</span></div></div><div><div><div>37</div></div><div><span>    </span><span>"""带参数的被装饰函数"""</span></div></div><div><div><div>38</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"Hello, </span><span>{</span><span>name</span><span>}</span><span>!"</span><span>)</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span><span>greet</span><span>(</span></span><span>"Alice"</span><span>)</span></div></div><div><div><div>41</div></div><div><span># 输出：</span></div></div><div><div><div>42</div></div><div><span># Something is happening before the function is called.</span></div></div><div><div><div>43</div></div><div><span># Hello, Alice!</span></div></div><div><div><div>44</div></div><div><span># Something is happening after the function is called.</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 带有返回值的函数装饰器 --"</span><span>)</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>@my_decorator</span></div></div><div><div><div>49</div></div><div><span>def</span><span> </span><span>add</span><span>(</span><span>a</span><span>,</span><span><span> </span><span>b</span></span><span>):</span></div></div><div><div><div>50</div></div><div><span>    </span><span>"""带返回值的被装饰函数"""</span></div></div><div><div><div>51</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"Adding </span><span>{</span><span>a</span><span>}</span><span> and </span><span>{</span><span>b</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>52</div></div><div><span>    </span><span>return</span><span><span> a </span><span>+</span><span> b</span></span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span><span>sum_result </span><span>=</span><span> </span><span>add</span><span>(</span></span><span>5</span><span>, </span><span>3</span><span>)</span></div></div><div><div><div>55</div></div><div><span>print</span><span>(</span><span>f</span><span>"Sum: </span><span>{</span><span>sum_result</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>56</div></div><div><span># 输出：</span></div></div><div><div><div>57</div></div><div><span># Something is happening before the function is called.</span></div></div><div><div><div>58</div></div><div><span># Adding 5 and 3</span></div></div><div><div><div>59</div></div><div><span># Something is happening after the function is called.</span></div></div><div><div><div>60</div></div><div><span># Sum: 8</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 带参数的装饰器 --"</span><span>)</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>def</span><span> </span><span>repeat</span><span>(</span><span>num_times</span><span>):</span></div></div><div><div><div>65</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>66</div></div><div><span><span>    </span></span><span>带参数的装饰器工厂函数</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>:param num_times: 重复执行的次数</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>:return: 装饰器函数</span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>70</div></div><div><span>    </span><span>def</span><span> </span><span>decorator_repeat</span><span>(</span><span>func</span><span>):</span></div></div><div><div><div>71</div></div><div><span>        </span><span>def</span><span> </span><span>wrapper</span><span>(*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>72</div></div><div><span>            </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(num_times):</span></div></div><div><div><div>73</div></div><div><span><span>                </span></span><span>result </span><span>=</span><span> </span><span>func</span><span>(*args, **kwargs)</span></div></div><div><div><div>74</div></div><div><span>            </span><span>return</span><span> result</span></div></div><div><div><div>75</div></div><div><span>        </span><span>return</span><span> wrapper</span></div></div><div><div><div>76</div></div><div><span>    </span><span>return</span><span> decorator_repeat</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>@repeat</span><span>(</span><span>num_times</span><span>=</span><span>3</span><span>)</span></div></div><div><div><div>79</div></div><div><span>def</span><span> </span><span>greet_repeated</span><span>(</span><span>name</span><span>):</span></div></div><div><div><div>80</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"Hello, </span><span>{</span><span>name</span><span>}</span><span>!"</span><span>)</span></div></div><div><div><div>81</div></div><div>
</div></div><div><div><div>82</div></div><div><span><span>greet_repeated</span><span>(</span></span><span>"Bob"</span><span>)</span></div></div><div><div><div>83</div></div><div><span># 输出：</span></div></div><div><div><div>84</div></div><div><span># Hello, Bob!</span></div></div><div><div><div>85</div></div><div><span># Hello, Bob!</span></div></div><div><div><div>86</div></div><div><span># Hello, Bob!</span></div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 类装饰器 --"</span><span>)</span></div></div><div><div><div>89</div></div><div>
</div></div><div><div><div>90</div></div><div><span>class</span><span><span> </span><span>CountCalls</span></span><span>:</span></div></div><div><div><div>91</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>类装饰器示例，记录函数调用次数</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>94</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>func</span></span><span>):</span></div></div><div><div><div>95</div></div><div><span>        </span><span>self</span><span><span>.func </span><span>=</span><span> func</span></span></div></div><div><div><div>96</div></div><div><span>        </span><span>self</span><span><span>.num_calls </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span>    </span><span>def</span><span> </span><span>__call__</span><span>(</span><span>self</span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>99</div></div><div><span>        </span><span>self</span><span><span>.num_calls </span><span>+=</span><span> </span></span><span>1</span></div></div><div><div><div>100</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"Call </span><span>{</span><span>self</span><span>.num_calls</span><span>}</span><span> of </span><span>{</span><span>self</span><span>.func.</span><span>__name__</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>101</div></div><div><span>        </span><span>return</span><span> </span><span>self</span><span><span>.</span><span>func</span><span>(*args, **kwargs)</span></span></div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div><span>@CountCalls</span></div></div><div><div><div>104</div></div><div><span>def</span><span> </span><span>say_hello_counted</span><span>():</span></div></div><div><div><div>105</div></div><div><span>    </span><span>print</span><span>(</span><span>"Hello!"</span><span>)</span></div></div><div><div><div>106</div></div><div>
</div></div><div><div><div>107</div></div><div><span><span>say_hello_counted</span><span>()</span></span></div></div><div><div><div>108</div></div><div><span># 输出：</span></div></div><div><div><div>109</div></div><div><span># Call 1 of say_hello_counted</span></div></div><div><div><div>110</div></div><div><span># Hello!</span></div></div><div><div><div>111</div></div><div>
</div></div><div><div><div>112</div></div><div><span><span>say_hello_counted</span><span>()</span></span></div></div><div><div><div>113</div></div><div><span># 输出：</span></div></div><div><div><div>114</div></div><div><span># Call 2 of say_hello_counted</span></div></div><div><div><div>115</div></div><div><span># Hello!</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>函数即对象</strong>: 在Python中，函数是第一类对象，可以作为参数传递给其他函数，也可以作为其他函数的返回值。</li>
<li><strong>闭包</strong>: 装饰器内部的 <code>wrapper</code> 函数是一个闭包，它可以访问其外部（<code>my_decorator</code> 函数）作用域的变量，即使外部函数已经执行完毕。</li>
<li><strong>语法糖 <code>@</code></strong>: 简化了装饰器的应用，使得代码更具可读性。</li>
<li><strong>链式装饰器</strong>: 可以将多个装饰器堆叠在一个函数上，它们会从下到上依次执行。</li>
<li><strong>带参数的装饰器</strong>: 装饰器本身也可以接受参数，这需要额外的嵌套层级来实现（装饰器工厂函数）。</li>
<li><strong>类装饰器</strong>: 类也可以作为装饰器，通过实现 <code>__call__</code> 方法来使类的实例可调用。</li>
<li><strong>functools.wraps</strong>: 使用 <code>@functools.wraps(func)</code> 装饰器可以保留原始函数的元数据（如 <code>__name__</code>, <code>__doc__</code> 等）。</li>
<li><strong>装饰器应用顺序</strong>: 当多个装饰器应用于同一个函数时，最内层的装饰器最先执行，最外层的装饰器最后执行。</li>
<li><strong>装饰器用途</strong>: 常见用途包括日志记录、性能测试、事务处理、缓存、权限校验、输入验证等。</li>
</ul></section></section><section><h3>生成器<a href="#生成器"><span>#</span></a></h3><p>生成器（Generator）是Python中一种特殊的迭代器，它允许你以惰性（lazy）的方式生成序列中的元素，而不是一次性生成所有元素并存储在内存中。这使得生成器在处理大量数据时非常高效，因为它只在需要时才生成下一个值，从而节省了大量的内存空间。生成器是实现迭代器协议的一种简洁方式，它们在迭代过程中按需生成数据，而不是预先计算并存储所有数据。</p><section><h4>生成器的创建<a href="#生成器的创建"><span>#</span></a></h4><p>生成器可以通过两种主要方式创建：</p><ol>
<li><strong>生成器函数（Generator Function）</strong>：包含 <code>yield</code> 关键字的函数。当函数中包含 <code>yield</code> 语句时，它就变成了一个生成器函数。每次调用 <code>yield</code> 时，函数会暂停执行并返回一个值，并在下次调用 <code>next()</code> 时从上次暂停的地方继续执行。</li>
<li><strong>生成器表达式（Generator Expression）</strong>：类似于列表推导式，但使用圆括号 <code>()</code> 而不是方括号 <code>[]</code>。它返回一个生成器对象，而不是一个列表。</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 生成器函数示例</span></div></div><div><div><div>2</div></div><div><span># my_generator 是一个生成器函数，因为它包含了 yield 关键字。</span></div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>my_generator</span><span>():</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(</span><span>"开始生成..."</span><span>)</span></div></div><div><div><div>5</div></div><div><span>    </span><span>yield</span><span> </span><span>1</span><span> </span><span># 第一次调用 next() 时，函数执行到这里，返回1，并暂停。</span></div></div><div><div><div>6</div></div><div><span>    </span><span>print</span><span>(</span><span>"继续生成..."</span><span>)</span></div></div><div><div><div>7</div></div><div><span>    </span><span>yield</span><span> </span><span>2</span><span> </span><span># 第二次调用 next() 时，从上次暂停的地方继续，执行到这里，返回2，并暂停。</span></div></div><div><div><div>8</div></div><div><span>    </span><span>print</span><span>(</span><span>"再次生成..."</span><span>)</span></div></div><div><div><div>9</div></div><div><span>    </span><span>yield</span><span> </span><span>3</span><span> </span><span># 第三次调用 next() 时，从上次暂停的地方继续，执行到这里，返回3，并暂停。</span></div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>(</span><span>"生成结束。"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 创建一个生成器对象</span></div></div><div><div><div>13</div></div><div><span><span>gen </span><span>=</span><span> </span><span>my_generator</span><span>()</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 使用 next() 函数获取生成器中的下一个值</span></div></div><div><div><div>16</div></div><div><span>print</span><span>(</span><span>f</span><span>"第一次 next(): </span><span>{</span><span>next</span><span>(gen)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>17</div></div><div><span>print</span><span>(</span><span>f</span><span>"第二次 next(): </span><span>{</span><span>next</span><span>(gen)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>18</div></div><div><span>print</span><span>(</span><span>f</span><span>"第三次 next(): </span><span>{</span><span>next</span><span>(gen)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span># 当没有更多值可生成时，再次调用 next() 会引发 StopIteration 异常。</span></div></div><div><div><div>21</div></div><div><span>try</span><span>:</span></div></div><div><div><div>22</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"第四次 next(): </span><span>{</span><span>next</span><span>(gen)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>23</div></div><div><span>except</span><span> </span><span>StopIteration</span><span>:</span></div></div><div><div><div>24</div></div><div><span>    </span><span>print</span><span>(</span><span>"生成器已耗尽，捕获到 StopIteration 异常。"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 使用 for 循环遍历生成器 --"</span><span>)</span></div></div><div><div><div>27</div></div><div><span># 生成器是可迭代的，所以可以使用 for 循环来遍历它，for 循环会自动处理 StopIteration 异常。</span></div></div><div><div><div>28</div></div><div><span>for</span><span> value </span><span>in</span><span><span> </span><span>my_generator</span><span>():</span></span></div></div><div><div><div>29</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"通过 for 循环获取的值: </span><span>{</span><span>value</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 2. 生成器表达式示例 --"</span><span>)</span></div></div><div><div><div>32</div></div><div><span># 创建一个生成器表达式，类似于列表推导式，但使用圆括号。</span></div></div><div><div><div>33</div></div><div><span># 它不会立即计算所有值，而是创建一个生成器对象。</span></div></div><div><div><div>34</div></div><div><span><span>gen_exp </span><span>=</span><span> (x </span><span>*</span><span> x </span></span><span>for</span><span> x </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>))</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>print</span><span>(</span><span>f</span><span>"生成器表达式对象: </span><span>{</span><span>gen_exp</span><span>}</span><span>"</span><span>) </span><span># 打印的是生成器对象本身，而不是值。</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span># 遍历生成器表达式获取值</span></div></div><div><div><div>39</div></div><div><span>print</span><span>(</span><span>"遍历生成器表达式:"</span><span>)</span></div></div><div><div><div>40</div></div><div><span>for</span><span> num </span><span>in</span><span> gen_exp:</span></div></div><div><div><div>41</div></div><div><span>    </span><span>print</span><span>(num)</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>-- 内存效率示例 --"</span><span>)</span></div></div><div><div><div>44</div></div><div><span># 比较列表推导式和生成器表达式的内存使用</span></div></div><div><div><div>45</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span># 列表推导式会一次性生成所有元素并存储在内存中</span></div></div><div><div><div>48</div></div><div><span><span>my_list </span><span>=</span><span> [i </span></span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>1000000</span><span>)]</span></div></div><div><div><div>49</div></div><div><span>print</span><span>(</span><span>f</span><span>"列表占用内存: </span><span>{</span><span><span>sys.</span><span>getsizeof</span><span>(my_list)</span></span><span>}</span><span> bytes"</span><span>)</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span># 生成器表达式只在需要时生成元素，内存占用很小</span></div></div><div><div><div>52</div></div><div><span><span>my_generator_exp </span><span>=</span><span> (i </span></span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>1000000</span><span>))</span></div></div><div><div><div>53</div></div><div><span>print</span><span>(</span><span>f</span><span>"生成器占用内存: </span><span>{</span><span><span>sys.</span><span>getsizeof</span><span>(my_generator_exp)</span></span><span>}</span><span> bytes"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>惰性计算（Lazy Evaluation）</strong>: 生成器只在请求时才生成下一个值，而不是一次性生成所有值。这种按需生成数据的特性使得生成器在处理无限序列或大数据集时非常有用。</li>
<li><strong><code>yield</code> 关键字</strong>: 是生成器函数的核心。当函数执行到 <code>yield</code> 语句时，它会暂停执行，将 <code>yield</code> 后面的表达式的值作为结果返回，并保存当前的所有状态（包括局部变量和指令指针）。下次调用 <code>next()</code> 时，函数会从上次暂停的地方继续执行。</li>
<li><strong>迭代器协议</strong>: 生成器自动实现了Python的迭代器协议，这意味着它们拥有 <code>__iter__()</code> 和 <code>__next__()</code> 方法。因此，生成器对象可以直接用于 <code>for</code> 循环、列表推导式以及其他需要迭代器的场景。</li>
<li><strong>内存效率</strong>: 生成器不会一次性将所有数据加载到内存中，而是逐个生成数据。这使得它们在处理大型数据集时具有极高的内存效率，避免了内存溢出的问题。</li>
<li><strong><code>StopIteration</code></strong>: 当生成器函数执行完毕，或者遇到 <code>return</code> 语句（没有返回值），或者没有更多的 <code>yield</code> 语句时，<code>next()</code> 函数会引发 <code>StopIteration</code> 异常。<code>for</code> 循环会自动捕获并处理这个异常，从而优雅地结束迭代。</li>
<li><strong>生成器表达式</strong>: 提供了一种简洁的方式来创建生成器，语法类似于列表推导式，但使用圆括号 <code>()</code>。它比列表推导式更节省内存，因为它不会立即构建整个列表。</li>
</ul></section></section><section><h3>上下文管理器<a href="#上下文管理器"><span>#</span></a></h3><p>上下文管理器（Context Manager）是Python中用于管理资源（如文件、网络连接、锁等）的一种机制，它确保资源在被使用后能够正确地获取和释放，即使在操作过程中发生错误也能保证资源的清理。这主要通过 <code>with</code> 语句来实现，它使得代码更加简洁、安全和易读。</p><section><h4>工作原理<a href="#工作原理"><span>#</span></a></h4><p>上下文管理器通过实现两个特殊方法来工作：</p><ul>
<li><code>__enter__(self)</code>: 在 <code>with</code> 语句块执行之前被调用。它负责设置资源，并返回一个值（通常是资源本身），这个值会被赋给 <code>as</code> 关键字后面的变量。</li>
<li><code>__exit__(self, exc_type, exc_val, exc_tb)</code>: 在 <code>with</code> 语句块执行完毕后（无论是正常结束还是发生异常）被调用。它负责清理资源。参数 <code>exc_type</code>, <code>exc_val</code>, <code>exc_tb</code> 分别表示异常类型、异常值和回溯信息。如果 <code>with</code> 块中没有发生异常，这三个参数都将是 <code>None</code>。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 自定义上下文管理器类</span></div></div><div><div><div>2</div></div><div><span># FileManager 类实现了上下文管理器协议，用于安全地处理文件。</span></div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>FileManager</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>一个自定义的文件上下文管理器，确保文件在使用后被正确关闭。</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>7</div></div><div><span>    </span><span># 构造函数，初始化文件名为 filename。</span></div></div><div><div><div>8</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>filename</span></span><span>,</span><span><span> </span><span>mode</span></span><span>=</span><span>'r'</span><span>):</span></div></div><div><div><div>9</div></div><div><span>        </span><span>self</span><span><span>.filename </span><span>=</span><span> filename</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>self</span><span><span>.mode </span><span>=</span><span> mode</span></span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.file </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span># 进入上下文时调用此方法。</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>__enter__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"进入上下文：尝试打开文件 </span><span>{</span><span>self</span><span>.filename</span><span>}</span><span> (模式: </span><span>{</span><span>self</span><span>.mode</span><span>}</span><span>)"</span><span>)</span></div></div><div><div><div>16</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>17</div></div><div><span>            </span><span>self</span><span><span>.file </span><span>=</span><span> </span></span><span>open</span><span>(</span><span>self</span><span>.filename, </span><span>self</span><span>.mode) </span><span># 打开文件。</span></div></div><div><div><div>18</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>19</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"打开文件失败: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>20</div></div><div><span>            </span><span># 重新抛出异常，或者根据需要处理</span></div></div><div><div><div>21</div></div><div><span>            </span><span>raise</span></div></div><div><div><div>22</div></div><div><span>        </span><span>return</span><span> </span><span>self</span><span>.file </span><span># 返回文件对象，它将被赋给 with 语句中的变量（如 f）。</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span># 退出上下文时调用此方法。</span></div></div><div><div><div>25</div></div><div><span>    </span><span># exc_type, exc_val, exc_tb 分别是异常类型、异常值和回溯信息。</span></div></div><div><div><div>26</div></div><div><span>    </span><span>def</span><span> </span><span>__exit__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>exc_type</span></span><span>,</span><span><span> </span><span>exc_val</span></span><span>,</span><span><span> </span><span>exc_tb</span></span><span>):</span></div></div><div><div><div>27</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"退出上下文：尝试关闭文件 </span><span>{</span><span>self</span><span>.filename</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>28</div></div><div><span>        </span><span>if</span><span> </span><span>self</span><span>.file: </span><span># 确保文件对象存在。</span></div></div><div><div><div>29</div></div><div><span>            </span><span>self</span><span><span>.file.</span><span>close</span><span>() </span></span><span># 关闭文件，释放资源。</span></div></div><div><div><div>30</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"文件 </span><span>{</span><span>self</span><span>.filename</span><span>}</span><span> 已成功关闭。"</span><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>        </span><span># 如果 __exit__ 方法返回 True，则表示已经处理了异常，异常不会继续传播。</span></div></div><div><div><div>33</div></div><div><span>        </span><span># 如果返回 False 或 None，则异常会继续传播。</span></div></div><div><div><div>34</div></div><div><span>        </span><span>if</span><span> exc_type: </span><span># 如果发生了异常</span></div></div><div><div><div>35</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"在上下文管理器中捕获到异常：</span><span>{</span><span>exc_type.</span><span>__name__</span><span>}</span><span>: </span><span>{</span><span>exc_val</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>36</div></div><div><span>            </span><span># 可以选择在这里处理异常，例如记录日志，然后返回 True 阻止异常传播。</span></div></div><div><div><div>37</div></div><div><span>            </span><span># return True # 如果返回 True，异常将被抑制。</span></div></div><div><div><div>38</div></div><div><span>        </span><span>return</span><span> </span><span>False</span><span> </span><span># 默认返回 False，让异常正常传播。</span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span># 使用自定义上下文管理器</span></div></div><div><div><div>41</div></div><div><span><span>file_path </span><span>=</span><span> </span></span><span>'my_test_file.txt'</span></div></div><div><div><div>42</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 写入文件示例 ---"</span><span>)</span></div></div><div><div><div>43</div></div><div><span>with</span><span><span> </span><span>FileManager</span><span>(file_path, </span></span><span>'w'</span><span>) </span><span>as</span><span> f: </span><span># 进入上下文，文件被打开。</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>'Hello, Python Context Manager!</span><span>\n</span><span>'</span><span>)</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>'This is a test line.</span><span>\n</span><span>'</span><span>)</span></div></div><div><div><div>46</div></div><div><span>print</span><span>(</span><span>f</span><span>"文件 '</span><span>{</span><span>file_path</span><span>}</span><span>' 已写入并关闭。"</span><span>) </span><span># 退出上下文，文件被关闭。</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 读取文件示例 ---"</span><span>)</span></div></div><div><div><div>49</div></div><div><span>with</span><span><span> </span><span>FileManager</span><span>(file_path, </span></span><span>'r'</span><span>) </span><span>as</span><span> f: </span><span># 进入上下文，文件被打开。</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>content </span><span>=</span><span> f.</span><span>read</span><span>()</span></div></div><div><div><div>51</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"文件 '</span><span>{</span><span>file_path</span><span>}</span><span>' 内容:</span><span>\n</span><span>{</span><span>content</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>52</div></div><div><span>print</span><span>(</span><span>f</span><span>"文件 '</span><span>{</span><span>file_path</span><span>}</span><span>' 已读取并关闭。"</span><span>) </span><span># 退出上下文，文件被关闭。</span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 异常处理示例 ---"</span><span>)</span></div></div><div><div><div>55</div></div><div><span>try</span><span>:</span></div></div><div><div><div>56</div></div><div><span>    </span><span>with</span><span><span> </span><span>FileManager</span><span>(file_path, </span></span><span>'r'</span><span>) </span><span>as</span><span> f: </span><span># 进入上下文</span></div></div><div><div><div>57</div></div><div><span>        </span><span>print</span><span>(</span><span>"尝试读取文件..."</span><span>)</span></div></div><div><div><div>58</div></div><div><span>        </span><span># 故意制造一个错误，例如尝试对文件对象进行不存在的操作</span></div></div><div><div><div>59</div></div><div><span><span>        </span></span><span>f.</span><span>non_existent_method</span><span>()</span></div></div><div><div><div>60</div></div><div><span>except</span><span> </span><span>AttributeError</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>61</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"在外部捕获到异常: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>62</div></div><div><span>print</span><span>(</span><span>"程序继续执行。"</span><span>)</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span># 2. 使用 contextlib 模块的 @contextmanager 装饰器</span></div></div><div><div><div>65</div></div><div><span># 对于简单的上下文管理器，可以使用 contextlib 模块的 @contextmanager 装饰器来简化实现。</span></div></div><div><div><div>66</div></div><div><span># 它将一个生成器函数转换为上下文管理器。</span></div></div><div><div><div>67</div></div><div><span>from</span><span> contextlib </span><span>import</span><span> contextmanager</span></div></div><div><div><div>68</div></div><div>
</div></div><div><div><div>69</div></div><div><span>@contextmanager</span></div></div><div><div><div>70</div></div><div><span>def</span><span> </span><span>open_file_context</span><span>(</span><span>path</span><span>,</span><span><span> </span><span>mode</span></span><span>):</span></div></div><div><div><div>71</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>72</div></div><div><span><span>    </span></span><span>使用 @contextmanager 装饰器实现的上下文管理器，用于文件操作。</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>74</div></div><div><span>    </span><span>file</span><span><span> </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>75</div></div><div><span>    </span><span>try</span><span>:</span></div></div><div><div><div>76</div></div><div><span>        </span><span>file</span><span><span> </span><span>=</span><span> </span></span><span>open</span><span>(path, mode)</span></div></div><div><div><div>77</div></div><div><span>        </span><span>yield</span><span> </span><span>file</span><span> </span><span># yield 之前是 __enter__ 的逻辑，yield 之后是 __exit__ 的逻辑。</span></div></div><div><div><div>78</div></div><div><span>    </span><span>finally</span><span>:</span></div></div><div><div><div>79</div></div><div><span>        </span><span>if</span><span> </span><span>file</span><span>:</span></div></div><div><div><div>80</div></div><div><span>            </span><span>file</span><span><span>.</span><span>close</span><span>()</span></span></div></div><div><div><div>81</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"文件 </span><span>{</span><span>path</span><span>}</span><span> 已通过 contextmanager 装饰器关闭。"</span><span>)</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 使用 @contextmanager 装饰器示例 ---"</span><span>)</span></div></div><div><div><div>84</div></div><div><span>with</span><span><span> </span><span>open_file_context</span><span>(</span></span><span>'another_test_file.txt'</span><span>, </span><span>'w'</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>85</div></div><div><span><span>    </span></span><span>f.</span><span>write</span><span>(</span><span>'Hello from contextmanager decorator!</span><span>\n</span><span>'</span><span>)</span></div></div><div><div><div>86</div></div><div><span>print</span><span>(</span><span>"文件 'another_test_file.txt' 已写入并关闭 (通过装饰器)。"</span><span>)</span></div></div><div><div><div>87</div></div><div>
</div></div><div><div><div>88</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 锁的上下文管理器示例 ---"</span><span>)</span></div></div><div><div><div>89</div></div><div><span>import</span><span> threading</span></div></div><div><div><div>90</div></div><div>
</div></div><div><div><div>91</div></div><div><span><span>lock </span><span>=</span><span> threading.</span><span>Lock</span><span>()</span></span></div></div><div><div><div>92</div></div><div>
</div></div><div><div><div>93</div></div><div><span># 使用 with 语句管理锁的获取和释放</span></div></div><div><div><div>94</div></div><div><span>with</span><span> lock:</span></div></div><div><div><div>95</div></div><div><span>    </span><span>print</span><span>(</span><span>"锁已被获取，执行临界区代码..."</span><span>)</span></div></div><div><div><div>96</div></div><div><span>    </span><span># 临界区代码</span></div></div><div><div><div>97</div></div><div><span>    </span><span>print</span><span>(</span><span>"临界区代码执行完毕。"</span><span>)</span></div></div><div><div><div>98</div></div><div><span># 离开 with 块时，锁会自动释放</span></div></div><div><div><div>99</div></div><div><span>print</span><span>(</span><span>"锁已自动释放。"</span><span>)</span></div></div><div><div><div>100</div></div><div>
</div></div><div><div><div>101</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 数据库连接的上下文管理器示例 (伪代码) ---"</span><span>)</span></div></div><div><div><div>102</div></div><div><span># 假设有一个数据库连接类</span></div></div><div><div><div>103</div></div><div><span>class</span><span><span> </span><span>DatabaseConnection</span></span><span>:</span></div></div><div><div><div>104</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>db_name</span></span><span>):</span></div></div><div><div><div>105</div></div><div><span>        </span><span>self</span><span><span>.db_name </span><span>=</span><span> db_name</span></span></div></div><div><div><div>106</div></div><div><span>        </span><span>self</span><span><span>.connection </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div><span>    </span><span>def</span><span> </span><span>__enter__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>109</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"连接到数据库: </span><span>{</span><span>self</span><span>.db_name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>110</div></div><div><span>        </span><span># 模拟数据库连接操作</span></div></div><div><div><div>111</div></div><div><span>        </span><span>self</span><span><span>.connection </span><span>=</span><span> </span></span><span>f</span><span>"Connection to </span><span>{</span><span>self</span><span>.db_name</span><span>}</span><span>"</span></div></div><div><div><div>112</div></div><div><span>        </span><span>return</span><span> </span><span>self</span><span>.connection</span></div></div><div><div><div>113</div></div><div>
</div></div><div><div><div>114</div></div><div><span>    </span><span>def</span><span> </span><span>__exit__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>exc_type</span></span><span>,</span><span><span> </span><span>exc_val</span></span><span>,</span><span><span> </span><span>exc_tb</span></span><span>):</span></div></div><div><div><div>115</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"关闭数据库连接: </span><span>{</span><span>self</span><span>.db_name</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>116</div></div><div><span>        </span><span># 模拟关闭数据库连接操作</span></div></div><div><div><div>117</div></div><div><span>        </span><span>self</span><span><span>.connection </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>118</div></div><div><span>        </span><span>if</span><span> exc_type:</span></div></div><div><div><div>119</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"数据库操作中发生异常: </span><span>{</span><span>exc_val</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>120</div></div><div>
</div></div><div><div><div>121</div></div><div><span>with</span><span><span> </span><span>DatabaseConnection</span><span>(</span></span><span>"my_database"</span><span>) </span><span>as</span><span> db:</span></div></div><div><div><div>122</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"执行数据库查询: </span><span>{</span><span>db</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>123</div></div><div><span>    </span><span># 模拟执行查询</span></div></div><div><div><div>124</div></div><div><span>    </span><span># raise ValueError("模拟数据库错误") # 可以测试异常处理</span></div></div><div><div><div>125</div></div><div><span>print</span><span>(</span><span>"数据库连接已关闭。"</span><span>)</span></div></div><div><div><div>126</div></div><div>
</div></div><div><div><div>127</div></div><div>
</div></div><div><div><div>128</div></div><div><span><span>**</span><span>核心概念：</span><span>**</span></span></div></div><div><div><div>129</div></div><div><span><span>*</span><span>   </span><span>**</span><span>资源管理</span><span>**</span><span>: 上下文管理器的核心目的是确保资源（如文件句柄、网络连接、数据库连接、线程锁等）在使用后能够被正确地获取和释放，即使在代码执行过程中发生异常也能保证资源的清理，从而避免资源泄露。</span></span></div></div><div><div><div>130</div></div><div><span><span>*</span><span>   </span><span>**</span></span><span>`</span><span>with</span><span>`</span><span><span> 语句</span><span>**</span><span>: Python 提供的一种语法糖，用于简化资源管理。它确保在进入 </span></span><span>`</span><span>with</span><span>`</span><span> 块时资源被正确设置，并在退出 </span><span>`</span><span>with</span><span>`</span><span> 块时（无论是正常退出还是因异常退出）资源被正确清理。</span></div></div><div><div><div>131</div></div><div><span><span>*</span><span>   </span><span>**</span></span><span>`</span><span>__enter__</span><span>`</span><span><span> 方法</span><span>**</span><span>: 当 </span></span><span>`</span><span>with</span><span>`</span><span> 语句执行时，首先调用上下文管理器的 </span><span>`</span><span>__enter__</span><span>`</span><span> 方法。此方法负责资源的获取和初始化，并返回一个值（通常是资源本身），这个值会被赋给 </span><span>`</span><span>as</span><span>`</span><span> 关键字后面的变量。</span></div></div><div><div><div>132</div></div><div><span><span>*</span><span>   </span><span>**</span></span><span>`</span><span>__exit__</span><span>`</span><span><span> 方法</span><span>**</span><span>: 当 </span></span><span>`</span><span>with</span><span>`</span><span> 语句块执行完毕或发生异常时，</span><span>`</span><span>__exit__</span><span>`</span><span> 方法会被调用。它负责资源的清理和释放。该方法接收三个参数：</span><span>`exc_type`</span><span>（异常类型）、</span><span>`exc_val`</span><span>（异常值）和 </span><span>`exc_tb`</span><span>（回溯信息）。如果 </span><span>`</span><span>with</span><span>`</span><span> 块中没有发生异常，这三个参数都将是 </span><span>`</span><span>None</span><span>`</span><span>。</span></div></div><div><div><div>133</div></div><div><span><span>*</span><span>   </span><span>**</span><span>异常安全</span><span>**</span><span>: </span></span><span>`</span><span>__exit__</span><span>`</span><span> 方法的强大之处在于其异常处理能力。即使 </span><span>`</span><span>with</span><span>`</span><span> 块内部发生未捕获的异常，</span><span>`</span><span>__exit__</span><span>`</span><span> 方法也总会被调用，从而保证资源得到清理。如果 </span><span>`</span><span>__exit__</span><span>`</span><span> 方法返回 </span><span>`</span><span>True</span><span>`</span><span>，则表示它已经处理了异常，该异常将不会向外传播；如果返回 </span><span>`</span><span>False</span><span>`</span><span> 或 </span><span>`</span><span>None</span><span>`</span><span>，则异常会继续传播。</span></div></div><div><div><div>134</div></div><div><span><span>*</span><span>   </span><span>**</span></span><span>`contextlib`</span><span><span> 模块</span><span>**</span><span>: Python 标准库中的 </span></span><span>`contextlib`</span><span> 模块提供了用于创建和使用上下文管理器的实用工具。其中最常用的是 </span><span>`</span><span>@</span><span>contextmanager`</span><span> 装饰器，它允许你使用简单的生成器函数来创建上下文管理器，而无需手动实现 </span><span>`</span><span>__enter__</span><span>`</span><span> 和 </span><span>`</span><span>__exit__</span><span>`</span><span> 方法，极大地简化了上下文管理器的编写。</span></div></div><div><div><div>135</div></div><div><span><span>*</span><span>   </span><span>**</span><span>常见应用场景</span><span>**</span><span>: 文件操作（自动关闭文件）、数据库连接（自动关闭连接）、线程锁（自动释放锁）、网络连接、临时目录创建与清理等。</span></span></div></div><div><div><div>136</div></div><div>
</div></div><div><div><div>137</div></div><div><span>### 多线程和多进程</span></div></div><div><div><div>138</div></div><div><span>在Python中，多线程（Multithreading）和多进程（Multiprocessing）是实现并发（Concurrency）和并行（Parallelism）的两种主要方式。它们允许程序同时执行多个任务，从而提高程序的效率和响应速度。理解这两种机制的差异和适用场景对于编写高性能的Python程序至关重要。</span></div></div><div><div><div>139</div></div><div>
</div></div><div><div><div>140</div></div><div><span>#### 1. 多线程 (Multithreading)</span></div></div><div><div><div>141</div></div><div><span>线程是进程中的一个执行单元。一个进程可以包含多个线程，这些线程共享进程的内存空间。Python的 </span><span>`threading`</span><span> 模块提供了多线程支持。</span></div></div><div><div><div>142</div></div><div>
</div></div><div><div><div>143</div></div><div><span><span>**</span><span>特点：</span><span>**</span></span></div></div><div><div><div>144</div></div><div><span><span>*</span><span>   </span><span>**</span><span>共享内存</span><span>**</span><span>: 线程共享进程的内存空间，因此数据共享方便，但需要注意线程安全问题，避免数据竞争。</span></span></div></div><div><div><div>145</div></div><div><span><span>*</span><span>   </span><span>**</span><span>GIL</span><span> (Global Interpreter Lock)</span><span>**</span><span>: Python的全局解释器锁（</span><span>GIL</span><span>）是一个互斥锁，它限制了在任何给定时刻只有一个线程可以执行Python字节码。这意味着Python多线程在CPU密集型任务上无法实现真正的并行计算（因为GIL会阻止多个线程同时执行Python代码），但在I</span><span>/</span><span>O密集型任务（如网络请求、文件读写）上仍然可以提高效率，因为在等待I</span><span>/</span><span>O时GIL会被释放，允许其他线程运行。</span></span></div></div><div><div><div>146</div></div><div><span><span>*</span><span>   </span><span>**</span><span>开销小</span><span>**</span><span>: 线程的创建和销毁开销相对较小，上下文切换速度快。</span></span></div></div><div><div><div>147</div></div><div>
</div></div><div><div><div>148</div></div><div><span>```python</span></div></div><div><div><div>149</div></div><div><span>import</span><span> threading </span><span># 导入Python的线程模块。</span></div></div><div><div><div>150</div></div><div><span>import</span><span> time      </span><span># 导入时间模块，用于模拟耗时操作。</span></div></div><div><div><div>151</div></div><div><span>import</span><span> os        </span><span># 导入os模块，用于获取进程ID。</span></div></div><div><div><div>152</div></div><div>
</div></div><div><div><div>153</div></div><div><span># 定义一个共享资源</span></div></div><div><div><div>154</div></div><div><span><span>shared_data </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>155</div></div><div><span># 创建一个锁对象，用于保护共享资源</span></div></div><div><div><div>156</div></div><div><span><span>data_lock </span><span>=</span><span> threading.</span><span>Lock</span><span>()</span></span></div></div><div><div><div>157</div></div><div>
</div></div><div><div><div>158</div></div><div><span># 定义一个线程函数，这将是每个线程执行的任务。</span></div></div><div><div><div>159</div></div><div><span>def</span><span> </span><span>thread_worker</span><span>(</span><span>name</span><span>,</span><span><span> </span><span>duration</span></span><span>):</span></div></div><div><div><div>160</div></div><div><span>    </span><span>global</span><span> shared_data</span></div></div><div><div><div>161</div></div><div><span><span>    </span></span><span>pid </span><span>=</span><span> os.</span><span>getpid</span><span>() </span><span># 获取当前进程的ID。所有线程都属于同一个进程，所以PID会相同。</span></div></div><div><div><div>162</div></div><div><span><span>    </span></span><span>thread_id </span><span>=</span><span> threading.</span><span>get_ident</span><span>() </span><span># 获取当前线程的唯一标识符。</span></div></div><div><div><div>163</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"线程 </span><span>{</span><span>name</span><span>}</span><span> (PID: </span><span>{</span><span>pid</span><span>}</span><span>, Thread ID: </span><span>{</span><span>thread_id</span><span>}</span><span>) 开始工作，预计持续 </span><span>{</span><span>duration</span><span>}</span><span> 秒..."</span><span>)</span></div></div><div><div><div>164</div></div><div><span><span>    </span></span><span>time.</span><span>sleep</span><span>(duration) </span><span># 模拟一个耗时的操作，例如网络请求或文件读写。</span></div></div><div><div><div>165</div></div><div>
</div></div><div><div><div>166</div></div><div><span>    </span><span># 使用锁来保护共享数据</span></div></div><div><div><div>167</div></div><div><span>    </span><span>with</span><span> data_lock:</span></div></div><div><div><div>168</div></div><div><span><span>        </span></span><span>shared_data </span><span>+=</span><span> </span><span>1</span></div></div><div><div><div>169</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"线程 </span><span>{</span><span>name</span><span>}</span><span> 更新共享数据，当前值为: </span><span>{</span><span>shared_data</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>170</div></div><div>
</div></div><div><div><div>171</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"线程 </span><span>{</span><span>name</span><span>}</span><span> (PID: </span><span>{</span><span>pid</span><span>}</span><span>, Thread ID: </span><span>{</span><span>thread_id</span><span>}</span><span>) 完成工作。"</span><span>)</span></div></div><div><div><div>172</div></div><div>
</div></div><div><div><div>173</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 多线程示例 ---"</span><span>)</span></div></div><div><div><div>174</div></div><div><span><span>threads </span><span>=</span><span> [] </span></span><span># 用于存储所有线程对象的列表。</span></div></div><div><div><div>175</div></div><div><span># 创建并启动多个线程。</span></div></div><div><div><div>176</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>3</span><span>):</span></div></div><div><div><div>177</div></div><div><span>    </span><span># threading.Thread 创建一个线程对象。</span></div></div><div><div><div>178</div></div><div><span>    </span><span># target 参数指定线程启动后要执行的函数（即 thread_worker）。</span></div></div><div><div><div>179</div></div><div><span>    </span><span># args 参数以元组形式传递给 target 函数的参数。这里传递了线程名称和模拟持续时间。</span></div></div><div><div><div>180</div></div><div><span><span>    </span></span><span>t </span><span>=</span><span> threading.</span><span>Thread</span><span>(</span><span>target</span><span><span>=</span><span>thread_worker, </span></span><span>args</span><span><span>=</span><span>(</span></span><span>f</span><span>"Thread-</span><span>{</span><span>i</span><span>}</span><span>"</span><span>, </span><span>1</span><span>))</span></div></div><div><div><div>181</div></div><div><span><span>    </span></span><span>threads.</span><span>append</span><span>(t) </span><span># 将创建的线程对象添加到列表中。</span></div></div><div><div><div>182</div></div><div><span><span>    </span></span><span>t.</span><span>start</span><span>() </span><span># 启动线程。调用 start() 方法后，线程会开始执行其 target 函数。</span></div></div><div><div><div>183</div></div><div>
</div></div><div><div><div>184</div></div><div><span># 等待所有线程完成。</span></div></div><div><div><div>185</div></div><div><span># t.join() 方法会阻塞当前线程（在这里是主线程），直到 t 线程执行完毕。</span></div></div><div><div><div>186</div></div><div><span># 确保所有子线程都执行完成后，主线程才继续执行后续代码。</span></div></div><div><div><div>187</div></div><div><span>for</span><span> t </span><span>in</span><span> threads:</span></div></div><div><div><div>188</div></div><div><span><span>    </span></span><span>t.</span><span>join</span><span>()</span></div></div><div><div><div>189</div></div><div><span>print</span><span>(</span><span>f</span><span>"所有线程任务完成。主线程继续执行。最终共享数据: </span><span>{</span><span>shared_data</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section><section><h3>多进程 (Multiprocessing) *<a href="#多进程-multiprocessing-"><span>#</span></a></h3><p>进程是操作系统分配资源（如内存、CPU时间）的基本单位。每个进程都有自己独立的内存空间。Python的 <code>multiprocessing</code> 模块提供了多进程支持，它通过创建新的进程来绕过GIL的限制，从而实现真正的并行计算。</p><p><strong>特点：</strong></p><ul>
<li><strong>独立内存</strong>: 每个进程有独立的内存空间，它们之间的数据默认是隔离的。因此，数据共享需要特定的机制（如队列 <code>Queue</code>、管道 <code>Pipe</code>、共享内存 <code>Value</code>/<code>Array</code>）进行进程间通信（IPC）。</li>
<li><strong>无GIL限制</strong>: 每个进程都有自己的Python解释器和GIL，因此在多核CPU上可以实现真正的并行计算，适用于CPU密集型任务（如复杂的数学计算、图像处理）。</li>
<li><strong>开销大</strong>: 进程的创建和销毁开销相对较大，上下文切换也比线程慢。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> multiprocessing </span><span># 导入Python的多进程模块。</span></div></div><div><div><div>2</div></div><div><span>import</span><span> time            </span><span># 导入时间模块，用于模拟耗时操作。</span></div></div><div><div><div>3</div></div><div><span>import</span><span> os              </span><span># 导入os模块，用于获取进程ID。</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 定义一个进程函数，这将是每个进程执行的任务。</span></div></div><div><div><div>6</div></div><div><span>def</span><span> </span><span>process_worker</span><span>(</span><span>name</span><span>,</span><span><span> </span><span>duration</span></span><span>,</span><span><span> </span><span>queue</span></span><span>):</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>pid </span><span>=</span><span> os.</span><span>getpid</span><span>() </span><span># 获取当前进程的ID。每个进程都会有不同的PID。</span></div></div><div><div><div>8</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"进程 </span><span>{</span><span>name</span><span>}</span><span> (PID: </span><span>{</span><span>pid</span><span>}</span><span>) 开始工作，预计持续 </span><span>{</span><span>duration</span><span>}</span><span> 秒..."</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>time.</span><span>sleep</span><span>(duration) </span><span># 模拟一个耗时的CPU密集型操作。</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> </span><span>f</span><span>"</span><span>{</span><span>name</span><span>}</span><span> 完成工作，PID: </span><span>{</span><span>pid</span><span>}</span><span>"</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>queue.</span><span>put</span><span>(result) </span><span># 将结果放入队列，用于进程间通信</span></div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"进程 </span><span>{</span><span>name</span><span>}</span><span> (PID: </span><span>{</span><span>pid</span><span>}</span><span>) 完成工作。"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 多进程示例 ---"</span><span>)</span></div></div><div><div><div>15</div></div><div><span><span>processes </span><span>=</span><span> [] </span></span><span># 用于存储所有进程对象的列表。</span></div></div><div><div><div>16</div></div><div><span># 创建一个队列，用于进程间通信</span></div></div><div><div><div>17</div></div><div><span><span>results_queue </span><span>=</span><span> multiprocessing.</span><span>Queue</span><span>()</span></span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># 创建并启动多个进程。</span></div></div><div><div><div>20</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>3</span><span>):</span></div></div><div><div><div>21</div></div><div><span>    </span><span># multiprocessing.Process 创建一个进程对象。</span></div></div><div><div><div>22</div></div><div><span>    </span><span># target 参数指定进程启动后要执行的函数（即 process_worker）。</span></div></div><div><div><div>23</div></div><div><span>    </span><span># args 参数以元组形式传递给 target 函数的参数。</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>p </span><span>=</span><span> multiprocessing.</span><span>Process</span><span>(</span><span>target</span><span><span>=</span><span>process_worker, </span></span><span>args</span><span><span>=</span><span>(</span></span><span>f</span><span>"Process-</span><span>{</span><span>i</span><span>}</span><span>"</span><span>, </span><span>1</span><span>, results_queue))</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>processes.</span><span>append</span><span>(p) </span><span># 将创建的进程对象添加到列表中。</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>p.</span><span>start</span><span>() </span><span># 启动进程。调用 start() 方法后，进程会开始执行其 target 函数。</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span># 等待所有进程完成。</span></div></div><div><div><div>29</div></div><div><span># p.join() 方法会阻塞当前进程（在这里是主进程），直到 p 进程执行完毕。</span></div></div><div><div><div>30</div></div><div><span># 确保所有子进程都执行完成后，主进程才继续执行后续代码。</span></div></div><div><div><div>31</div></div><div><span>for</span><span> p </span><span>in</span><span> processes:</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>p.</span><span>join</span><span>()</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>print</span><span>(</span><span>"所有进程任务完成。主进程继续执行。"</span><span>)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span># 从队列中获取所有进程的结果</span></div></div><div><div><div>37</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 进程通信结果 --- "</span><span>)</span></div></div><div><div><div>38</div></div><div><span>while</span><span> </span><span>not</span><span><span> results_queue.</span><span>empty</span><span>():</span></span></div></div><div><div><div>39</div></div><div><span>    </span><span>print</span><span><span>(results_queue.</span><span>get</span><span>())</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>并发 vs. 并行</strong>:
<ul>
<li><strong>并发（Concurrency）</strong>: 指的是在同一时间段内处理多个任务的能力，通过任务的快速切换（例如，多线程在单核CPU上通过时间片轮转实现）。任务看起来是同时进行的，但实际上在任何一个瞬间只有一个任务在执行。</li>
<li><strong>并行（Parallelism）</strong>: 指的是在同一时刻真正同时执行多个任务的能力，通常需要多核CPU或多台机器。多进程在多核CPU上可以实现真正的并行。</li>
</ul>
</li>
<li><strong>GIL (Global Interpreter Lock)</strong>: 全局解释器锁（Global Interpreter Lock）。Python解释器的一个特性，它确保在任何时候只有一个线程在执行Python字节码。这是Python多线程无法实现CPU密集型任务并行的主要原因。对于I/O密集型任务，GIL会在等待I/O时释放，因此多线程仍然有效。</li>
<li><strong>线程同步与进程同步</strong>:
<ul>
<li><strong>线程同步</strong>: 由于多线程共享进程的内存空间，多个线程同时访问和修改共享数据可能导致数据不一致或错误（即”竞态条件”）。因此，需要使用同步机制来保护共享资源，确保数据操作的原子性。常见的线程同步机制包括：
<ul>
<li><strong>锁（<code>threading.Lock</code>）</strong>: 最基本的同步原语，用于保护临界区，确保一次只有一个线程访问共享资源。</li>
<li><strong>递归锁（<code>threading.RLock</code>）</strong>: 允许同一个线程多次获取同一把锁，适用于一个线程中需要多次获取锁的场景。</li>
<li><strong>条件变量（<code>threading.Condition</code>）</strong>: 允许线程等待某个条件满足，并在条件满足时被其他线程唤醒。</li>
<li><strong>信号量（<code>threading.Semaphore</code>）</strong>: 用于控制对共享资源的访问数量，例如限制同时访问数据库连接的线程数。</li>
<li><strong>事件（<code>threading.Event</code>）</strong>: 线程之间通信的简单标志，一个线程发出事件，其他线程等待事件。</li>
</ul>
</li>
<li><strong>进程同步</strong>: 类似于线程同步，但由于进程间内存独立，同步机制通常涉及操作系统提供的原语或 <code>multiprocessing</code> 模块提供的工具，如 <code>multiprocessing.Lock</code>、<code>multiprocessing.Semaphore</code> 等。</li>
</ul>
</li>
<li><strong>进程间通信 (IPC)</strong>: 由于多进程拥有独立的内存空间，它们之间需要通过特定的机制进行数据交换和协调。常见的IPC机制包括：
<ul>
<li><strong>队列（<code>multiprocessing.Queue</code>）</strong>: 最常用的IPC方式，适用于多生产者-多消费者模型，进程可以将数据放入队列，其他进程可以从队列中取出数据。</li>
<li><strong>管道（<code>multiprocessing.Pipe</code>）</strong>: 用于两个进程之间的双向通信。</li>
<li><strong>共享内存（<code>multiprocessing.Value</code> 和 <code>multiprocessing.Array</code>）</strong>: 允许不同进程直接读写同一块内存区域，但需要手动处理同步问题。</li>
<li><strong>管理器（<code>multiprocessing.Manager</code>）</strong>: 提供了一种创建可以在不同进程之间共享的Python对象的方式，例如列表、字典等，并自动处理同步问题。</li>
</ul>
</li>
<li><strong>适用场景</strong>:
<ul>
<li><strong>多线程</strong>: 适用于I/O密集型任务，例如网络请求、文件读写、数据库操作等，因为在等待I/O时可以释放GIL，允许其他线程执行。由于GIL的存在，不适合CPU密集型任务。</li>
<li><strong>多进程</strong>: 适用于CPU密集型任务，例如科学计算、图像处理、数据分析等，因为每个进程都有独立的解释器和GIL，可以充分利用多核CPU的并行处理能力。由于进程创建和销毁开销较大，不适合频繁创建和销毁的场景。</li>
</ul>
</li>
</ul></section><section><h3>异步编程 *<a href="#异步编程-"><span>#</span></a></h3><p>异步编程（Asynchronous Programming）是一种编程范式，它允许程序在等待某些操作（如I/O操作、网络请求、数据库查询等）完成时，不阻塞主线程，而是去执行其他任务。当等待的操作完成后，程序会回到之前暂停的地方继续执行。这与传统的同步编程（一步一步执行，每一步都阻塞直到完成）形成对比，能够显著提高程序的并发性和响应速度，尤其是在I/O密集型应用中。</p><p>Python通过 <code>asyncio</code> 库提供了对异步编程的原生支持，并引入了 <code>async</code> 和 <code>await</code> 关键字。</p><section><h4>核心概念<a href="#核心概念"><span>#</span></a></h4><ul>
<li><strong>协程 (Coroutines)</strong>: 使用 <code>async def</code> 定义的函数称为协程。协程是可暂停和可恢复的函数，它们在遇到 <code>await</code> 表达式时暂停执行，等待异步操作完成，然后从暂停点继续执行。</li>
<li><strong>事件循环 (Event Loop)</strong>: 异步编程的核心。它负责管理和调度协程的执行，当一个协程暂停等待I/O时，事件循环会切换到另一个准备就绪的协程执行。</li>
<li><strong><code>async</code> 关键字</strong>: 用于定义一个协程函数。</li>
<li><strong><code>await</code> 关键字</strong>: 只能在协程函数内部使用，用于暂停当前协程的执行，等待一个可等待对象（如另一个协程、<code>asyncio.sleep()</code>、<code>asyncio.gather()</code> 等）完成。</li>
<li><strong>可等待对象 (Awaitables)</strong>: 可以被 <code>await</code> 的对象，包括协程、Task 和 Future。</li>
<li><strong>任务 (Tasks)</strong>: <code>asyncio.create_task()</code> 用于将协程包装成一个 <code>Task</code> 对象，并将其安排到事件循环中运行。Task 是 Future 的子类，用于并发调度协程。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> asyncio</span></div></div><div><div><div>2</div></div><div><span>import</span><span> time</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 定义一个异步函数（协程）</span></div></div><div><div><div>5</div></div><div><span># async def 关键字表示这是一个协程函数。</span></div></div><div><div><div>6</div></div><div><span>async</span><span> </span><span>def</span><span> </span><span>fetch_data</span><span>(</span><span>delay</span><span>):</span></div></div><div><div><div>7</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 开始获取数据，预计延迟 </span><span>{</span><span>delay</span><span>}</span><span> 秒..."</span><span>)</span></div></div><div><div><div>8</div></div><div><span>    </span><span># await asyncio.sleep(delay) 暂停当前协程的执行，等待指定秒数。</span></div></div><div><div><div>9</div></div><div><span>    </span><span># 在等待期间，事件循环可以切换到其他协程执行，实现非阻塞。</span></div></div><div><div><div>10</div></div><div><span>    </span><span>await</span><span><span> asyncio.</span><span>sleep</span><span>(delay)</span></span></div></div><div><div><div>11</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 数据获取完成，延迟 </span><span>{</span><span>delay</span><span>}</span><span> 秒。"</span><span>)</span></div></div><div><div><div>12</div></div><div><span>    </span><span>return</span><span> </span><span>f</span><span>"Data after </span><span>{</span><span>delay</span><span>}</span><span>s"</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 定义主异步函数</span></div></div><div><div><div>15</div></div><div><span>async</span><span> </span><span>def</span><span> </span><span>main</span><span>():</span></div></div><div><div><div>16</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 主程序开始运行。"</span><span>)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span># 创建任务：将协程包装成 Task 对象，并将其安排到事件循环中运行。</span></div></div><div><div><div>19</div></div><div><span>    </span><span># 任务会立即开始执行，但不会阻塞当前协程。</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>task1 </span><span>=</span><span> asyncio.</span><span>create_task</span><span>(</span><span>fetch_data</span><span>(</span><span>2</span><span>)) </span><span># 创建一个延迟2秒的任务。</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>task2 </span><span>=</span><span> asyncio.</span><span>create_task</span><span>(</span><span>fetch_data</span><span>(</span><span>1</span><span>)) </span><span># 创建一个延迟1秒的任务。</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>    </span><span># 等待任务完成：asyncio.gather() 并发地运行多个可等待对象，并等待它们全部完成。</span></div></div><div><div><div>24</div></div><div><span>    </span><span># 它会收集所有任务的结果，并以列表形式返回。</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>results </span><span>=</span><span> </span><span>await</span><span><span> asyncio.</span><span>gather</span><span>(task1, task2)</span></span></div></div><div><div><div>26</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 所有任务完成: </span><span>{</span><span>results</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 另一个异步函数示例 ---"</span><span>)</span></div></div><div><div><div>29</div></div><div><span>    </span><span>await</span><span><span> </span><span>another_async_function</span><span>()</span></span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>async</span><span> </span><span>def</span><span> </span><span>another_async_function</span><span>():</span></div></div><div><div><div>32</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 另一个异步函数开始。"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>    </span><span>await</span><span><span> asyncio.</span><span>sleep</span><span>(</span></span><span>0.5</span><span>)</span></div></div><div><div><div>34</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span><span>time.</span><span>strftime</span><span>(</span></span><span>'%H:%M:%S'</span><span>)</span><span>}</span><span>] 另一个异步函数结束。"</span><span>)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span># 运行主异步函数</span></div></div><div><div><div>37</div></div><div><span># asyncio.run(main()) 是运行异步程序的入口点。它负责启动事件循环，运行 main 协程，并在 main 协程完成后关闭事件循环。</span></div></div><div><div><div>38</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>asyncio.</span><span>run</span><span>(</span><span>main</span><span>())</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span># 预期输出（时间戳可能不同，但顺序和并发性是关键）：</span></div></div><div><div><div>42</div></div><div><span># [HH:MM:SS] 主程序开始运行。</span></div></div><div><div><div>43</div></div><div><span># [HH:MM:SS] 开始获取数据，预计延迟 2 秒...</span></div></div><div><div><div>44</div></div><div><span># [HH:MM:SS] 开始获取数据，预计延迟 1 秒。</span></div></div><div><div><div>45</div></div><div><span># [HH:MM:SS] 数据获取完成，延迟 1 秒。</span></div></div><div><div><div>46</div></div><div><span># [HH:MM:SS] 数据获取完成，延迟 2 秒。</span></div></div><div><div><div>47</div></div><div><span># [HH:MM:SS] 所有任务完成: ['Data after 2s', 'Data after 1s']</span></div></div><div><div><div>48</div></div><div><span># [HH:MM:SS] 另一个异步函数开始。</span></div></div><div><div><div>49</div></div><div><span># [HH:MM:SS] 另一个异步函数结束。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>非阻塞I/O</strong>: 异步编程的核心优势，允许程序在等待I/O时执行其他任务。</li>
<li><strong><code>async</code> 和 <code>await</code></strong>: Python中定义和使用协程的关键关键字。</li>
<li><strong>事件循环</strong>: 异步任务的调度器。</li>
<li><strong>并发而非并行</strong>: 异步编程实现的是并发（通过任务切换），而不是真正的并行（同时执行）。真正的并行通常需要多进程。</li>
<li><strong>适用场景</strong>: 适用于I/O密集型任务，如网络爬虫、Web服务器、数据库操作等。</li>
</ul></section></section><section><h3>单元测试 *<a href="#单元测试-"><span>#</span></a></h3><p>单元测试（Unit Testing）是软件开发中的一种测试方法，用于验证程序中最小的可测试单元（通常是函数、方法或类）是否按预期工作。通过编写单元测试，可以确保代码的质量、减少bug、提高代码的可维护性，并为代码重构提供安全保障。</p><p>Python标准库提供了 <code>unittest</code> 模块，这是一个功能丰富的单元测试框架，类似于Java的JUnit或NUnit。</p><section><h4><code>unittest</code> 模块的基本组成：<a href="#unittest-模块的基本组成"><span>#</span></a></h4><ul>
<li><strong>测试用例 (Test Case)</strong>: 继承自 <code>unittest.TestCase</code> 的类。每个测试用例类通常包含一个或多个测试方法。</li>
<li><strong>测试方法 (Test Method)</strong>: 测试用例类中以 <code>test_</code> 开头的方法。每个测试方法都是一个独立的测试。</li>
<li><strong>断言 (Assertions)</strong>: <code>unittest.TestCase</code> 类提供了各种断言方法（如 <code>assertEqual</code>, <code>assertTrue</code>, <code>assertRaises</code> 等），用于检查测试结果是否符合预期。</li>
<li><strong>测试固件 (Test Fixtures)</strong>: <code>setUp()</code> 和 <code>tearDown()</code> 方法。<code>setUp()</code> 在每个测试方法执行前运行，用于准备测试环境；<code>tearDown()</code> 在每个测试方法执行后运行，用于清理测试环境。</li>
<li><strong>测试套件 (Test Suite)</strong>: 用于组织和运行多个测试用例。</li>
<li><strong>测试运行器 (Test Runner)</strong>: 用于执行测试套件中的测试，并报告结果。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> unittest </span><span># 导入 Python 内置的单元测试框架。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># 待测试的类或函数</span></div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>Calculator</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""一个简单的计算器类，用于演示单元测试。"""</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>add</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>a</span></span><span>,</span><span><span> </span><span>b</span></span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>"""返回两个数的和。"""</span></div></div><div><div><div>8</div></div><div><span>        </span><span>return</span><span><span> a </span><span>+</span><span> b</span></span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>subtract</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>a</span></span><span>,</span><span><span> </span><span>b</span></span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>"""返回两个数的差。"""</span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span><span> a </span><span>-</span><span> b</span></span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>multiply</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>a</span></span><span>,</span><span><span> </span><span>b</span></span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>"""返回两个数的乘积。"""</span></div></div><div><div><div>16</div></div><div><span>        </span><span>return</span><span><span> a </span><span>*</span><span> b</span></span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>def</span><span> </span><span>divide</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>a</span></span><span>,</span><span><span> </span><span>b</span></span><span>):</span></div></div><div><div><div>19</div></div><div><span>        </span><span>"""返回两个数的商。"""</span></div></div><div><div><div>20</div></div><div><span>        </span><span>if</span><span><span> b </span><span>==</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>21</div></div><div><span>            </span><span>raise</span><span> </span><span>ValueError</span><span>(</span><span>"除数不能为零！"</span><span>)</span></div></div><div><div><div>22</div></div><div><span>        </span><span>return</span><span><span> a </span><span>/</span><span> b</span></span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 编写测试用例</span></div></div><div><div><div>25</div></div><div><span># TestCalculator 类继承自 unittest.TestCase，表示这是一个测试用例。</span></div></div><div><div><div>26</div></div><div><span>class</span><span><span> </span><span>TestCalculator</span><span>(</span><span>unittest</span><span>.</span><span>TestCase</span><span>)</span></span><span>:</span></div></div><div><div><div>27</div></div><div><span>    </span><span># setUp 方法在每个测试方法执行前运行，用于初始化测试环境。</span></div></div><div><div><div>28</div></div><div><span>    </span><span>def</span><span> </span><span>setUp</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>29</div></div><div><span>        </span><span>print</span><span>(</span><span>"</span><span>\n</span><span>Setting up Calculator instance..."</span><span>)</span></div></div><div><div><div>30</div></div><div><span>        </span><span>self</span><span><span>.calc </span><span>=</span><span> </span><span>Calculator</span><span>() </span></span><span># 为每个测试方法创建一个新的 Calculator 实例。</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span># tearDown 方法在每个测试方法执行后运行，用于清理测试环境。</span></div></div><div><div><div>33</div></div><div><span>    </span><span>def</span><span> </span><span>tearDown</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>34</div></div><div><span>        </span><span>print</span><span>(</span><span>"Tearing down Calculator instance..."</span><span>)</span></div></div><div><div><div>35</div></div><div><span>        </span><span>del</span><span> </span><span>self</span><span>.calc </span><span># 删除 Calculator 实例。</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>    </span><span># 测试方法必须以 test_ 开头。</span></div></div><div><div><div>38</div></div><div><span>    </span><span>def</span><span> </span><span>test_add</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>39</div></div><div><span>        </span><span>print</span><span>(</span><span>"Running test_add..."</span><span>)</span></div></div><div><div><div>40</div></div><div><span>        </span><span># 使用 assertEqual 断言方法检查 add 方法的返回值是否符合预期。</span></div></div><div><div><div>41</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>add</span><span>(</span></span><span>3</span><span>, </span><span>5</span><span>), </span><span>8</span><span>) </span><span># 3 + 5 应该等于 8。</span></div></div><div><div><div>42</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>add</span><span>(</span><span>-</span></span><span>1</span><span>, </span><span>1</span><span>), </span><span>0</span><span>) </span><span># -1 + 1 应该等于 0。</span></div></div><div><div><div>43</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>add</span><span>(</span><span>-</span></span><span>1</span><span><span>, </span><span>-</span></span><span>1</span><span><span>), </span><span>-</span></span><span>2</span><span>) </span><span># -1 + -1 应该等于 -2。</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>    </span><span>def</span><span> </span><span>test_subtract</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>46</div></div><div><span>        </span><span>print</span><span>(</span><span>"Running test_subtract..."</span><span>)</span></div></div><div><div><div>47</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>subtract</span><span>(</span></span><span>10</span><span>, </span><span>5</span><span>), </span><span>5</span><span>)</span></div></div><div><div><div>48</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>subtract</span><span>(</span><span>-</span></span><span>1</span><span><span>, </span><span>-</span></span><span>1</span><span>), </span><span>0</span><span>)</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>    </span><span>def</span><span> </span><span>test_multiply</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>51</div></div><div><span>        </span><span>print</span><span>(</span><span>"Running test_multiply..."</span><span>)</span></div></div><div><div><div>52</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>multiply</span><span>(</span></span><span>2</span><span>, </span><span>3</span><span>), </span><span>6</span><span>)</span></div></div><div><div><div>53</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>multiply</span><span>(</span><span>-</span></span><span>2</span><span>, </span><span>3</span><span><span>), </span><span>-</span></span><span>6</span><span>)</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>    </span><span>def</span><span> </span><span>test_divide</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>56</div></div><div><span>        </span><span>print</span><span>(</span><span>"Running test_divide..."</span><span>)</span></div></div><div><div><div>57</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>divide</span><span>(</span></span><span>10</span><span>, </span><span>2</span><span>), </span><span>5</span><span>)</span></div></div><div><div><div>58</div></div><div><span>        </span><span>self</span><span><span>.</span><span>assertAlmostEqual</span><span>(</span></span><span>self</span><span><span>.calc.</span><span>divide</span><span>(</span></span><span>10</span><span>, </span><span>3</span><span>), </span><span>3.333333</span><span>, </span><span>places</span><span>=</span><span>6</span><span>) </span><span># 浮点数比较使用 assertAlmostEqual。</span></div></div><div><div><div>59</div></div><div><span>        </span><span># 测试异常：使用 assertRaises 断言方法检查是否抛出了预期的异常。</span></div></div><div><div><div>60</div></div><div><span>        </span><span>with</span><span> </span><span>self</span><span><span>.</span><span>assertRaises</span><span>(</span></span><span>ValueError</span><span>): </span><span># 期望当除数为零时抛出 ValueError。</span></div></div><div><div><div>61</div></div><div><span>            </span><span>self</span><span><span>.calc.</span><span>divide</span><span>(</span></span><span>10</span><span>, </span><span>0</span><span>)</span></div></div><div><div><div>62</div></div><div>
</div></div><div><div><div>63</div></div><div><span># 运行测试</span></div></div><div><div><div>64</div></div><div><span># if __name__ == '__main__': 确保只有在直接运行此脚本时才执行测试。</span></div></div><div><div><div>65</div></div><div><span># unittest.main() 发现并运行当前文件中所有的测试用例和测试方法。</span></div></div><div><div><div>66</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>'__main__'</span><span>:</span></div></div><div><div><div>67</div></div><div><span><span>    </span></span><span>unittest.</span><span>main</span><span>(</span><span>argv</span><span><span>=</span><span>[</span></span><span>'first-arg-is-ignored'</span><span>], </span><span>exit</span><span>=</span><span>False</span><span>) </span><span># exit=False 避免在Jupyter等环境中退出。</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>测试驱动开发 (TDD)</strong>: 一种开发方法，先写测试，再写代码，然后重构。</li>
<li><strong>测试覆盖率</strong>: 衡量测试代码执行了多少生产代码的指标。</li>
<li><strong>回归测试</strong>: 在代码修改后，重新运行旧的测试，以确保修改没有引入新的bug或破坏现有功能。</li>
<li><strong>Mocking/Patching</strong>: 在测试中模拟（mock）或替换（patch）依赖项，以便隔离被测试单元。</li>
<li><strong>持续集成 (CI)</strong>: 将自动化测试集成到开发流程中，每次代码提交后自动运行测试。</li>
</ul></section></section><section><h3>文档字符串和类型提示<a href="#文档字符串和类型提示"><span>#</span></a></h3><p>在Python中，文档字符串（Docstrings）和类型提示（Type Hints）是提高代码可读性、可维护性以及协作效率的重要工具。它们有助于开发者更好地理解代码的功能、预期输入和输出，并在开发过程中提供静态分析和错误检查。</p><section><h4>1. 文档字符串 (Docstrings)<a href="#1-文档字符串-docstrings"><span>#</span></a></h4><p>文档字符串是Python中用于解释模块、类、函数或方法用途的字符串字面量。它们通常位于定义的第一行，并用三引号（<code>"""</code> 或 <code>'''</code>）括起来。文档字符串可以通过 <code>__doc__</code> 属性访问，并且可以被工具（如Sphinx、PyCharm）用于自动生成文档或提供上下文帮助。</p><p><strong>推荐的文档字符串格式：</strong>
Python社区通常推荐使用 reStructuredText 或 Google 风格的文档字符串，其中包含：</p><ul>
<li><strong>简要摘要</strong>: 一句话描述功能。</li>
<li><strong>详细描述</strong>: 更详细的解释，包括算法、设计选择等。</li>
<li><strong>参数 (Args)</strong>: 列出所有参数及其类型和描述。</li>
<li><strong>返回值 (Returns)</strong>: 描述返回值及其类型。</li>
<li><strong>异常 (Raises)</strong>: 列出可能抛出的异常及其条件。</li>
<li><strong>示例 (Examples)</strong>: 提供使用示例。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> typing </span><span>import</span><span> List, Dict, Union </span><span># 导入类型提示所需的模块。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># 示例：带有文档字符串和类型提示的函数</span></div></div><div><div><div>4</div></div><div><span>def</span><span> </span><span>calculate_average</span><span>(</span><span>numbers</span><span>:</span><span> List[Union[</span><span>int</span><span>, </span><span>float</span><span>]]</span><span>) -&gt; </span><span>float</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"""计算数字列表的平均值。</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>此函数接受一个包含整数或浮点数的列表，并返回它们的算术平均值。</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>如果输入列表为空，则会引发 ValueError。</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>numbers (List[Union[int, float]]): 包含数字（整数或浮点数）的列表。</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>float: 列表中所有数字的算术平均值。</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>Raises:</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>ValueError: 如果 `numbers` 列表为空。</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>Examples:</span></div></div><div><div><div>20</div></div><div><span>        </span><span>&gt;&gt;&gt; </span><span>calculate_average([1, 2, 3, 4, 5])</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>3.0</span></div></div><div><div><div>22</div></div><div><span>        </span><span>&gt;&gt;&gt; </span><span>calculate_average([10, 20, 30])</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>20.0</span></div></div><div><div><div>24</div></div><div><span>        </span><span>&gt;&gt;&gt; </span><span>calculate_average([])</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>Traceback (most recent call last):</span></div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>...</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>ValueError: 列表不能为空</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>29</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> numbers:</span></div></div><div><div><div>30</div></div><div><span>        </span><span>raise</span><span> </span><span>ValueError</span><span>(</span><span>"列表不能为空"</span><span>)</span></div></div><div><div><div>31</div></div><div><span>    </span><span>return</span><span> </span><span>sum</span><span><span>(numbers) </span><span>/</span><span> </span></span><span>len</span><span>(numbers)</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span># 访问文档字符串</span></div></div><div><div><div>34</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 文档字符串示例 ---"</span><span>)</span></div></div><div><div><div>35</div></div><div><span>print</span><span>(calculate_average.</span><span>__doc__</span><span>)</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span># 示例：带有文档字符串的类</span></div></div><div><div><div>38</div></div><div><span>class</span><span><span> </span><span>MyClass</span></span><span>:</span></div></div><div><div><div>39</div></div><div><span>    </span><span>"""这是一个示例类，演示文档字符串的使用。"""</span></div></div><div><div><div>40</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>name</span></span><span>:</span><span> </span><span>str</span><span>):</span></div></div><div><div><div>41</div></div><div><span>        </span><span>"""初始化 MyClass 实例。</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>Args:</span></div></div><div><div><div>44</div></div><div><span><span>            </span></span><span>name (str): 实例的名称。</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>46</div></div><div><span>        </span><span>self</span><span><span>.name </span><span>=</span><span> name</span></span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>    </span><span>def</span><span> </span><span>get_name</span><span>(</span><span>self</span><span>) -&gt; </span><span>str</span><span>:</span></div></div><div><div><div>49</div></div><div><span>        </span><span>"""获取实例的名称。</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>Returns:</span></div></div><div><div><div>52</div></div><div><span><span>            </span></span><span>str: 实例的名称。</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>54</div></div><div><span>        </span><span>return</span><span> </span><span>self</span><span>.name</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 类和方法的文档字符串示例 ---"</span><span>)</span></div></div><div><div><div>57</div></div><div><span>print</span><span>(MyClass.</span><span>__doc__</span><span>)</span></div></div><div><div><div>58</div></div><div><span>print</span><span>(MyClass.get_name.</span><span>__doc__</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h4>2. 类型提示 (Type Hints)<a href="#2-类型提示-type-hints"><span>#</span></a></h4><p>类型提示（在PEP 484中引入）允许开发者在代码中声明变量、函数参数和返回值的预期类型。Python本身是动态类型语言，不会在运行时强制执行类型检查，但类型提示可以被静态类型检查工具（如MyPy、Pyright）用于在代码运行前发现潜在的类型错误，提高代码的可靠性。同时，它们也极大地增强了代码的可读性，并为IDE提供了更好的自动补全和重构支持。</p><p><strong>类型提示的优点：</strong></p><ul>
<li><strong>提高代码可读性</strong>: 明确函数预期接收的参数类型和返回的类型，使代码意图更清晰。</li>
<li><strong>增强代码可维护性</strong>: 减少因类型不匹配导致的运行时错误，便于团队协作和代码重构。</li>
<li><strong>静态分析</strong>: 配合静态类型检查工具（如MyPy、Pyright），可以在代码运行前发现潜在的类型错误。</li>
<li><strong>IDE支持</strong>: 现代IDE（如PyCharm、VS Code）可以利用类型提示提供更智能的代码补全、导航和重构功能。</li>
<li><strong>文档生成</strong>: 类型提示可以作为代码文档的一部分，自动生成API文档。</li>
</ul><p><strong>常用类型和 <code>typing</code> 模块：</strong>
Python的内置类型（如 <code>int</code>, <code>float</code>, <code>str</code>, <code>list</code>, <code>dict</code>）可以直接用于类型提示。对于更复杂的类型，<code>typing</code> 模块提供了丰富的类型提示工具：</p><ul>
<li><strong>基本类型</strong>: <code>int</code>, <code>float</code>, <code>str</code>, <code>bool</code>, <code>bytes</code>, <code>None</code>。</li>
<li><strong>集合类型</strong>:
<ul>
<li><code>List[T]</code>: 列表，例如 <code>List[int]</code> 表示只包含整数的列表。</li>
<li><code>Dict[K, V]</code>: 字典，例如 <code>Dict[str, int]</code> 表示键为字符串、值为整数的字典。</li>
<li><code>Tuple[T1, T2, ...]</code>: 元组，例如 <code>Tuple[str, int, bool]</code> 表示包含一个字符串、一个整数和一个布尔值的元组。对于任意长度的同类型元组，可以使用 <code>Tuple[int, ...]</code>。</li>
<li><code>Set[T]</code>: 集合，例如 <code>Set[str]</code> 表示只包含字符串的集合。</li>
</ul>
</li>
<li><strong>可选类型</strong>: <code>Optional[T]</code> 等同于 <code>Union[T, None]</code>，表示该值可以是类型 <code>T</code> 或 <code>None</code>。</li>
<li><strong>联合类型</strong>: <code>Union[T1, T2, ...]</code> 表示该值可以是列出类型中的任意一种。</li>
<li><strong>函数类型</strong>: <code>Callable[[Arg1Type, Arg2Type], ReturnType]</code> 用于提示函数作为参数的类型。</li>
<li><strong>任意类型</strong>: <code>Any</code> 表示可以是任何类型，通常用于类型信息不明确或需要兼容旧代码的情况。</li>
<li><strong>泛型</strong>: <code>TypeVar</code> 用于创建泛型函数或类，使其能够处理多种类型而保持类型安全。</li>
<li><strong>协议</strong>: <code>Protocol</code> 用于定义结构化类型，即只要对象具有某些方法或属性，就符合该类型，而无需显式继承。</li>
</ul><p><strong>类型提示的实践：</strong></p><ul>
<li><strong>逐步引入</strong>: 可以在现有项目中逐步引入类型提示，不必一次性完成所有代码的修改。</li>
<li><strong>保持更新</strong>: 当代码逻辑或数据结构发生变化时，及时更新类型提示。</li>
<li><strong>使用工具</strong>: 结合MyPy、Pyright等静态类型检查工具，在开发过程中发现并修复类型错误。</li>
<li><strong>文档一致性</strong>: 确保文档字符串中的类型信息与类型提示保持一致。</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 导入常用的类型提示</span></div></div><div><div><div>2</div></div><div><span>from</span><span> typing </span><span>import</span><span> List, Dict, Tuple, Set, Optional, Union, Callable, Any, TypeVar, Protocol</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 变量类型提示</span></div></div><div><div><div>5</div></div><div><span>def</span><span> </span><span>process_data</span><span>(</span><span>data</span><span>:</span><span> Dict[</span><span>str</span><span>, List[</span><span>int</span><span>]]</span><span>) -&gt; </span><span>None</span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""处理一个字典，其中键是字符串，值是整数列表。</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>data: 要处理的字典数据，格式为 {字符串: 整数列表}</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>11</div></div><div><span>    </span><span>for</span><span> key, values </span><span>in</span><span><span> data.</span><span>items</span><span>():</span></span></div></div><div><div><div>12</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"Key: </span><span>{</span><span>key</span><span>}</span><span>, Values: </span><span>{</span><span>values</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 函数参数和返回值类型提示</span></div></div><div><div><div>15</div></div><div><span>def</span><span> </span><span>greet</span><span>(</span><span>name</span><span>:</span><span> </span><span>str</span><span>) -&gt; </span><span>str</span><span>:</span></div></div><div><div><div>16</div></div><div><span>    </span><span>"""根据提供的名称返回一个问候语。</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>name: 要问候的名字</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>格式化后的问候字符串</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>24</div></div><div><span>    </span><span>return</span><span> </span><span>f</span><span>"Hello, </span><span>{</span><span>name</span><span>}</span><span>!"</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span># 可选参数</span></div></div><div><div><div>27</div></div><div><span>def</span><span> </span><span>get_user_info</span><span>(</span><span>user_id</span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>active</span></span><span>:</span><span> Optional[</span><span>bool</span><span>] </span><span>=</span><span><span> </span><span>None</span></span><span>) -&gt; Dict[</span><span>str</span><span>, Any]:</span></div></div><div><div><div>28</div></div><div><span>    </span><span>"""获取用户信息，active参数是可选的。</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>user_id: 用户ID</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>active: 可选的活动状态</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>包含用户信息的字典</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>info </span><span>=</span><span> {</span><span>"id"</span><span>: user_id, </span><span>"name"</span><span>: </span><span>f</span><span>"User</span><span>{</span><span>user_id</span><span>}</span><span>"</span><span>}</span></div></div><div><div><div>38</div></div><div><span>    </span><span>if</span><span> active </span><span>is</span><span> </span><span>not</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>info[</span><span>"active"</span><span><span>] </span><span>=</span><span> active</span></span></div></div><div><div><div>40</div></div><div><span>    </span><span>return</span><span> info</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span># 联合类型</span></div></div><div><div><div>43</div></div><div><span>def</span><span> </span><span>process_item</span><span>(</span><span>item</span><span>:</span><span> Union[</span><span>str</span><span>, </span><span>int</span><span>]</span><span>) -&gt; </span><span>str</span><span>:</span></div></div><div><div><div>44</div></div><div><span>    </span><span>"""处理一个可以是字符串或整数的项。</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>item: 要处理的项，可以是字符串或整数</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>50</div></div><div><span><span>        </span></span><span>处理后的字符串结果</span></div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>52</div></div><div><span>    </span><span>if</span><span> </span><span>isinstance</span><span>(item, </span><span>str</span><span>):</span></div></div><div><div><div>53</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"Processed string: </span><span>{</span><span><span>item.</span><span>upper</span><span>()</span></span><span>}</span><span>"</span></div></div><div><div><div>54</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>55</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"Processed number: </span><span>{</span><span><span>item </span><span>*</span><span> </span></span><span>2}</span><span>"</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span># 函数作为参数</span></div></div><div><div><div>58</div></div><div><span>def</span><span> </span><span>apply_function</span><span>(</span><span>func</span><span>:</span><span> Callable[[</span><span>int</span><span>, </span><span>int</span><span>], </span><span>int</span><span>]</span><span>,</span><span><span> </span><span>a</span></span><span>:</span><span> </span><span>int</span><span>,</span><span><span> </span><span>b</span></span><span>:</span><span> </span><span>int</span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>59</div></div><div><span>    </span><span>"""将一个接受两个整数并返回一个整数的函数应用于a和b。</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>func: 要应用的函数</span></div></div><div><div><div>63</div></div><div><span><span>        </span></span><span>a: 第一个参数</span></div></div><div><div><div>64</div></div><div><span><span>        </span></span><span>b: 第二个参数</span></div></div><div><div><div>65</div></div><div>
</div></div><div><div><div>66</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>函数应用后的结果</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>69</div></div><div><span>    </span><span>return</span><span><span> </span><span>func</span><span>(a, b)</span></span></div></div><div><div><div>70</div></div><div>
</div></div><div><div><div>71</div></div><div><span># 泛型</span></div></div><div><div><div>72</div></div><div><span><span>T </span><span>=</span><span> </span><span>TypeVar</span><span>(</span></span><span>'T'</span><span>)</span></div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span>def</span><span> </span><span>first_element</span><span>(</span><span>data</span><span>:</span><span> List[T]</span><span>) -&gt; Optional[T]:</span></div></div><div><div><div>75</div></div><div><span>    </span><span>"""返回列表中第一个元素，如果列表为空则返回None。</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>78</div></div><div><span><span>        </span></span><span>data: 要处理的列表</span></div></div><div><div><div>79</div></div><div>
</div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>81</div></div><div><span><span>        </span></span><span>第一个元素或None</span></div></div><div><div><div>82</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>83</div></div><div><span>    </span><span>if</span><span> data:</span></div></div><div><div><div>84</div></div><div><span>        </span><span>return</span><span> data[</span><span>0</span><span>]</span></div></div><div><div><div>85</div></div><div><span>    </span><span>return</span><span> </span><span>None</span></div></div><div><div><div>86</div></div><div>
</div></div><div><div><div>87</div></div><div><span># 协议</span></div></div><div><div><div>88</div></div><div><span>class</span><span><span> </span><span>SupportsLen</span><span>(</span><span>Protocol</span><span>)</span></span><span>:</span></div></div><div><div><div>89</div></div><div><span>    </span><span>def</span><span> </span><span>__len__</span><span>(</span><span>self</span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>90</div></div><div><span><span>        </span></span><span>...</span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span>def</span><span> </span><span>get_length</span><span>(</span><span>obj</span><span>:</span><span> SupportsLen</span><span>) -&gt; </span><span>int</span><span>:</span></div></div><div><div><div>93</div></div><div><span>    </span><span>"""返回支持len()操作的对象的长度。</span></div></div><div><div><div>94</div></div><div>
</div></div><div><div><div>95</div></div><div><span><span>    </span></span><span>Args:</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>obj: 支持长度协议的对象</span></div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span><span>    </span></span><span>Returns:</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>对象的长度</span></div></div><div><div><div>100</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>101</div></div><div><span>    </span><span>return</span><span> </span><span>len</span><span>(obj)</span></div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div><span># 示例调用</span></div></div><div><div><div>104</div></div><div><span>print</span><span>(</span><span>"</span><span>\n</span><span>--- 类型提示示例 ---"</span><span>)</span></div></div><div><div><div>105</div></div><div><span># 变量类型示例</span></div></div><div><div><div>106</div></div><div><span><span>process_data</span><span>({</span></span><span>"a"</span><span>: [</span><span>1</span><span>, </span><span>2</span><span>], </span><span>"b"</span><span>: [</span><span>3</span><span>, </span><span>4</span><span>, </span><span>5</span><span>]})</span></div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div><span># 函数参数和返回值示例</span></div></div><div><div><div>109</div></div><div><span>print</span><span><span>(</span><span>greet</span><span>(</span></span><span>"Alice"</span><span>))  </span><span># 输出: Hello, Alice!</span></div></div><div><div><div>110</div></div><div>
</div></div><div><div><div>111</div></div><div><span># 可选参数示例</span></div></div><div><div><div>112</div></div><div><span>print</span><span><span>(</span><span>get_user_info</span><span>(</span></span><span>101</span><span>))  </span><span># 输出: {'id': 101, 'name': 'User101'}</span></div></div><div><div><div>113</div></div><div><span>print</span><span><span>(</span><span>get_user_info</span><span>(</span></span><span>102</span><span>, </span><span>active</span><span>=</span><span>True</span><span>))  </span><span># 输出: {'id': 102, 'name': 'User102', 'active': True}</span></div></div><div><div><div>114</div></div><div>
</div></div><div><div><div>115</div></div><div><span># 联合类型示例</span></div></div><div><div><div>116</div></div><div><span>print</span><span><span>(</span><span>process_item</span><span>(</span></span><span>"hello"</span><span>))  </span><span># 输出: Processed string: HELLO</span></div></div><div><div><div>117</div></div><div><span>print</span><span><span>(</span><span>process_item</span><span>(</span></span><span>123</span><span>))  </span><span># 输出: Processed number: 246</span></div></div><div><div><div>118</div></div><div>
</div></div><div><div><div>119</div></div><div><span># 函数作为参数示例</span></div></div><div><div><div>120</div></div><div><span>print</span><span><span>(</span><span>apply_function</span><span>(</span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>+</span><span> y, </span></span><span>5</span><span>, </span><span>3</span><span>))  </span><span># 输出: 8</span></div></div><div><div><div>121</div></div><div>
</div></div><div><div><div>122</div></div><div><span># 泛型示例</span></div></div><div><div><div>123</div></div><div><span>print</span><span><span>(</span><span>first_element</span><span>([</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>]))  </span><span># 输出: 1</span></div></div><div><div><div>124</div></div><div><span>print</span><span><span>(</span><span>first_element</span><span>([</span></span><span>"a"</span><span>, </span><span>"b"</span><span>]))  </span><span># 输出: a (泛型T自动推断为str)</span></div></div><div><div><div>125</div></div><div>
</div></div><div><div><div>126</div></div><div><span># 协议示例</span></div></div><div><div><div>127</div></div><div><span>print</span><span><span>(</span><span>get_length</span><span>(</span></span><span>"world"</span><span>))  </span><span># 输出: 5</span></div></div><div><div><div>128</div></div><div><span>print</span><span><span>(</span><span>get_length</span><span>([</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>, </span><span>4</span><span>]))  </span><span># 输出: 4</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>自文档化</strong>: 文档字符串使代码本身成为其最佳文档。</li>
<li><strong>静态分析</strong>: 类型提示允许工具在代码运行前发现潜在错误。</li>
<li><strong>IDE支持</strong>: 增强IDE的自动补全、重构和错误检查功能。</li>
<li><strong>代码可读性与可维护性</strong>: 明确的文档和类型信息使代码更容易理解和维护。</li>
<li><strong>PEP 257</strong>: Python文档字符串的约定。</li>
<li><strong>PEP 484</strong>: Python类型提示的规范。</li>
</ul></section></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://shasnow.top/posts/pyside-study/</id>
      <title type="text">PySide6 学习笔记</title>
      <published>2025-06-20T00:00:00.000Z</published>
      <updated>2025-11-27T00:00:00.000Z</updated>
      <author><name>Shasnow</name></author>
      <link rel="alternate" href="https://shasnow.top/posts/pyside-study/"/>
      <summary type="text">本教程介绍了PySide6的基础知识和使用方法，包括窗口与组件、布局管理、Qt Designer的使用以及信号与槽机制等内容，帮助初学者快速上手PySide6进行GUI开发。</summary>
      <content type="html"><![CDATA[<section><h1>PySide6<a href="#pyside6"><span>#</span></a></h1><p>PySide6是一个Python绑定库，用于创建和管理基于Qt的用户界面。它提供了一组Python类和函数，使开发人员能够使用Python语言来创建和操作Qt应用程序的用户界面。</p><p>此教程将以初学者角度讲解PySide6的基础使用，并重点介绍Qt Designer的应用。</p><p><em>标有 * 的为选学内容</em></p><section><h2>基础<a href="#基础"><span>#</span></a></h2><p>PySide6 是 Qt 框架的 Python 绑定，用于创建跨平台的 GUI 应用程序。</p><section><h3>窗口与组件<a href="#窗口与组件"><span>#</span></a></h3><p>PySide6 提供了多种 GUI 组件，如按钮、文本框、标签等，可以组合创建复杂的用户界面。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QMainWindow, QPushButton</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>MainWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>6</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"My App"</span><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>button </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"Click Me!"</span><span>)</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>button.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.button_clicked)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(button)</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>def</span><span> </span><span>button_clicked</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>16</div></div><div><span>        </span><span>print</span><span>(</span><span>"Button was clicked!"</span><span>)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></span></div></div><div><div><div>19</div></div><div><span><span>window </span><span>=</span><span> </span><span>MainWindow</span><span>()</span></span></div></div><div><div><div>20</div></div><div><span><span>window.</span><span>show</span><span>()</span></span></div></div><div><div><div>21</div></div><div><span><span>app.</span><span>exec</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>PySide6应用程序的基本结构包含三个主要部分：</p><ol>
<li>QApplication对象管理整个应用程序的生命周期和控制流</li>
<li>QMainWindow作为主窗口提供基本的窗口框架</li>
<li>信号与槽机制用于处理用户交互和组件通信</li>
</ol></section><section><h3>布局管理<a href="#布局管理"><span>#</span></a></h3><p>PySide6 提供了多种布局管理器来组织窗口中的组件。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QVBoxLayout, QWidget, QLabel</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>MyWindow</span><span>(</span><span>QWidget</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>()</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>label1 </span><span>=</span><span> </span><span>QLabel</span><span>(</span><span>"Label 1"</span><span>)</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>label2 </span><span>=</span><span> </span><span>QLabel</span><span>(</span><span>"Label 2"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(label1)</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(label2)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setLayout</span><span>(layout)</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>PySide6提供了多种布局管理器来组织界面元素：</p><ul>
<li>QVBoxLayout将组件按垂直方向排列</li>
<li>QHBoxLayout将组件按水平方向排列</li>
<li>QGridLayout以网格形式排列组件，适合复杂布局</li>
</ul></section></section><section><h2>Qt Designer<a href="#qt-designer"><span>#</span></a></h2></section><section><h2>Qt Designer 详细教程<a href="#qt-designer-详细教程"><span>#</span></a></h2><section><h3>1. Qt Designer简介<a href="#1-qt-designer简介"><span>#</span></a></h3><p>Qt Designer是Qt官方提供的可视化界面设计工具，它允许开发者通过拖放组件的方式快速构建GUI界面，而无需手动编写大量布局代码。使用Qt Designer可以显著提高开发效率，特别适合初学者快速上手。</p></section><section><h3>2. 安装与启动<a href="#2-安装与启动"><span>#</span></a></h3><p>安装PySide6时会自动包含Qt Designer工具。启动方式如下：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 启动Qt Designer</span></div></div><div><div><div>2</div></div><div><span>pyside6-designer</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3. 界面设计基础<a href="#3-界面设计基础"><span>#</span></a></h3><ol>
<li>
<p><strong>选择模板</strong>：</p>
<ul>
<li>启动后首先选择窗口类型，常用选项有：
<ul>
<li>MainWindow：带菜单栏、工具栏的主窗口</li>
<li>Widget：通用空白窗口</li>
<li>Dialog：对话框窗口</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>添加组件</strong>：</p>
<ul>
<li>从左侧组件面板拖拽所需组件到设计区域</li>
<li>常用组件包括：
<ul>
<li>Buttons：按钮类组件</li>
<li>Input Widgets：输入类组件</li>
<li>Display Widgets：显示类组件</li>
<li>Layouts：布局管理器</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>属性设置</strong>：</p>
<ul>
<li>选中组件后，在右侧属性编辑器中调整属性</li>
<li>重要属性包括：
<ul>
<li>objectName：组件对象名称（在代码中引用）</li>
<li>geometry：位置和大小</li>
<li>text：显示文本</li>
<li>styleSheet：CSS样式</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>布局管理</strong>：</p>
<ul>
<li>使用布局管理器（如垂直、水平、网格布局）组织组件</li>
<li>右键点击窗口/容器 → 布局 → 选择布局类型</li>
</ul>
</li>
<li>
<p><strong>信号与槽</strong>：</p>
<ul>
<li>点击工具栏的”编辑信号/槽”按钮</li>
<li>拖动组件到空白处设置交互</li>
<li>例如：按钮点击事件连接到槽函数</li>
</ul>
</li>
<li>
<p><strong>保存设计</strong>：</p>
<ul>
<li>文件 → 保存（.ui格式）</li>
<li>.ui文件是XML格式的界面描述文件</li>
</ul>
</li>
</ol></section><section><h3>在Python中使用.ui文件<a href="#在python中使用ui文件"><span>#</span></a></h3><p>有两种主要方法可以在PySide6应用程序中使用.ui文件：</p></section><section><h3>4. 在Python中使用Qt Designer设计的界面<a href="#4-在python中使用qt-designer设计的界面"><span>#</span></a></h3><p>PySide6提供了两种方式加载.ui文件，各有优缺点：</p><section><h4>方法1：动态加载（StarRailAssistant 正在使用的方式）<a href="#方法1动态加载starrailassistant-正在使用的方式"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtUiTools </span><span>import</span><span> QUiLoader  </span><span># 用于动态加载.ui文件</span></div></div><div><div><div>4</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> QFile  </span><span># 用于文件操作</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 1. 创建应用程序对象</span></div></div><div><div><div>7</div></div><div><span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 2. 加载.ui文件</span></div></div><div><div><div>10</div></div><div><span><span>ui_file </span><span>=</span><span> </span><span>QFile</span><span>(</span></span><span>"mainwindow.ui"</span><span>)  </span><span># 指定.ui文件路径</span></div></div><div><div><div>11</div></div><div><span>if</span><span> </span><span>not</span><span><span> ui_file.</span><span>open</span><span>(QFile.ReadOnly):  </span></span><span># 以只读方式打开文件</span></div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>(</span><span>"无法打开.ui文件"</span><span>)</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 3. 使用QUiLoader加载界面</span></div></div><div><div><div>16</div></div><div><span><span>loader </span><span>=</span><span> </span><span>QUiLoader</span><span>()</span></span></div></div><div><div><div>17</div></div><div><span><span>window </span><span>=</span><span> loader.</span><span>load</span><span>(ui_file)  </span></span><span># 加载界面</span></div></div><div><div><div>18</div></div><div><span><span>ui_file.</span><span>close</span><span>()  </span></span><span># 关闭文件</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>if</span><span> </span><span>not</span><span> window:  </span><span># 检查是否加载成功</span></div></div><div><div><div>21</div></div><div><span>    </span><span>print</span><span><span>(loader.</span><span>errorString</span><span>())</span></span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 4. 显示窗口并启动事件循环</span></div></div><div><div><div>25</div></div><div><span><span>window.</span><span>show</span><span>()</span></span></div></div><div><div><div>26</div></div><div><span><span>sys.</span><span>exit</span><span>(app.</span><span>exec</span><span>())  </span></span><span># 启动应用程序</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>优点</strong>：</p><ul>
<li>无需预编译.ui文件</li>
<li>修改界面后无需重新生成代码</li>
<li>适合快速原型开发</li>
</ul></section><section><h4>方法2：预编译为Python代码<a href="#方法2预编译为python代码"><span>#</span></a></h4><ol>
<li>首先将.ui文件转换为Python代码：</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 将mainwindow.ui转换为ui_mainwindow.py</span></div></div><div><div><div>2</div></div><div><span>pyside6-uic</span><span> </span><span>mainwindow.ui</span><span> </span><span>-o</span><span> </span><span>ui_mainwindow.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>在程序中使用生成的界面类：</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QMainWindow</span></div></div><div><div><div>3</div></div><div><span>from</span><span> ui_mainwindow </span><span>import</span><span> Ui_MainWindow  </span><span># 导入生成的界面类</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>MainWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>7</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>        </span><span># 1. 创建界面实例</span></div></div><div><div><div>10</div></div><div><span>        </span><span>self</span><span><span>.ui </span><span>=</span><span> </span><span>Ui_MainWindow</span><span>()</span></span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>        </span><span># 2. 设置界面（自动创建所有组件）</span></div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.ui.</span><span>setupUi</span><span>(</span></span><span>self</span><span>)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>        </span><span># 3. 添加业务逻辑</span></div></div><div><div><div>16</div></div><div><span>        </span><span># 通过self.ui访问界面组件，如按钮、文本框等</span></div></div><div><div><div>17</div></div><div><span>        </span><span>self</span><span><span>.ui.pushButton.clicked.</span><span>connect</span><span>(</span></span><span>self</span><span>.on_button_click)</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>def</span><span> </span><span>on_button_click</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>20</div></div><div><span>        </span><span>"""按钮点击事件处理函数"""</span></div></div><div><div><div>21</div></div><div><span>        </span><span>print</span><span>(</span><span>"按钮被点击了!"</span><span>)</span></div></div><div><div><div>22</div></div><div><span>        </span><span>self</span><span><span>.ui.label.</span><span>setText</span><span>(</span></span><span>"Hello PySide6!"</span><span>)</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 应用程序入口</span></div></div><div><div><div>25</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>MainWindow</span><span>()</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>exec</span><span>())  </span><span># 启动事件循环</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>优点</strong>：</p><ul>
<li>代码更直观，可直接查看界面结构</li>
<li>类型检查和代码补全更好</li>
<li>适合大型项目</li>
</ul></section></section><section><h3>自定义组件与Qt Designer集成<a href="#自定义组件与qt-designer集成"><span>#</span></a></h3><p>您可以将自定义组件集成到Qt Designer中，使其可以在设计界面时使用：</p><ol>
<li>创建自定义组件类</li>
<li>创建一个插件类，继承自QPyDesignerCustomWidgetCollection</li>
<li>设置环境变量PYSIDE_DESIGNER_PLUGINS指向插件目录</li>
</ol><div><figure><figcaption><span>customwidget.py</span></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QPushButton</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>CustomButton</span><span>(</span><span>QPushButton</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>parent</span></span><span>=</span><span>None</span><span>):</span></div></div><div><div><div>5</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(parent)</span></div></div><div><div><div>6</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setStyleSheet</span><span>(</span></span><span>"background-color: #4CAF50; color: white;"</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># customplugin.py</span></div></div><div><div><div>9</div></div><div><span>from</span><span> PySide6.QtDesigner </span><span>import</span><span> QPyDesignerCustomWidgetCollection</span></div></div><div><div><div>10</div></div><div><span>from</span><span> customwidget </span><span>import</span><span> CustomButton</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 注册自定义组件</span></div></div><div><div><div>13</div></div><div><span><span>QPyDesignerCustomWidgetCollection.</span><span>addCustomWidget</span><span>(CustomButton)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>进阶<a href="#进阶"><span>#</span></a></h2><section><h3>5. 信号与槽机制详解<a href="#5-信号与槽机制详解"><span>#</span></a></h3><p>信号与槽是PySide6中组件间通信的核心机制，采用发布-订阅模式：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> Signal, QObject  </span><span># 导入信号相关类</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># 1. 定义信号发布者</span></div></div><div><div><div>4</div></div><div><span>class</span><span><span> </span><span>Communicate</span><span>(</span><span>QObject</span><span>)</span></span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span># 声明一个信号，参数类型为str</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>speak </span><span>=</span><span> </span><span>Signal</span><span>(</span><span>str</span><span>)  </span><span># 相当于定义了一个可以发送字符串的事件</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 2. 定义信号接收者</span></div></div><div><div><div>9</div></div><div><span>class</span><span><span> </span><span>Speaker</span><span>(</span><span>QObject</span><span>)</span></span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>    </span><span># 槽函数 - 接收信号的处理方法</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>say</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>message</span></span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"收到消息: </span><span>{</span><span>message</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 3. 创建对象实例</span></div></div><div><div><div>18</div></div><div><span><span>com </span><span>=</span><span> </span><span>Communicate</span><span>()  </span></span><span># 信号发送者</span></div></div><div><div><div>19</div></div><div><span><span>speaker </span><span>=</span><span> </span><span>Speaker</span><span>()  </span></span><span># 信号接收者</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span># 4. 建立信号与槽的连接</span></div></div><div><div><div>22</div></div><div><span><span>com.speak.</span><span>connect</span><span>(speaker.say)  </span></span><span># 将speak信号连接到say槽</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 5. 触发信号</span></div></div><div><div><div>25</div></div><div><span><span>com.speak.</span><span>emit</span><span>(</span></span><span>"Hello PySide6!"</span><span>)  </span><span># 发射信号，自动调用连接的槽函数</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>关键点说明：</strong></p><ol>
<li>
<p><strong>信号(Signal)</strong>：</p>
<ul>
<li>必须继承自QObject的类才能定义信号</li>
<li>使用<code>Signal(类型)</code>声明，支持多种参数类型</li>
<li>信号本身不包含实现逻辑，只负责通知</li>
</ul>
</li>
<li>
<p><strong>槽(Slot)</strong>：</p>
<ul>
<li>可以是任何Python可调用对象（方法、函数等）</li>
<li>参数类型必须与信号匹配</li>
<li>一个信号可以连接多个槽，一个槽也可以接收多个信号</li>
</ul>
</li>
<li>
<p><strong>连接与发射</strong>：</p>
<ul>
<li><code>connect()</code>建立信号与槽的关联</li>
<li><code>emit()</code>触发信号并传递参数</li>
<li>连接可以随时用<code>disconnect()</code>断开</li>
</ul>
</li>
</ol></section><section><h3>6. 创建自定义组件<a href="#6-创建自定义组件"><span>#</span></a></h3><p>通过继承现有组件并添加自定义功能，可以创建可重用的UI组件：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QPushButton</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>class</span><span><span> </span><span>CustomButton</span><span>(</span><span>QPushButton</span><span>)</span></span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>自定义按钮组件，继承自QPushButton</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>特点：</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>- 预设绿色主题样式</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>- 可直接在构造函数设置文本</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>10</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>text</span></span><span>):</span></div></div><div><div><div>11</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>(text)  </span><span># 调用父类构造函数初始化</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>        </span><span># 设置按钮样式表(QSS语法)</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setStyleSheet</span><span>(</span></span><span>"""</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>/* 按钮默认状态样式 */</span></div></div><div><div><div>16</div></div><div><span><span>            </span></span><span>QPushButton {</span></div></div><div><div><div>17</div></div><div><span><span>                </span></span><span>background-color: #4CAF50;  /* 绿色背景 */</span></div></div><div><div><div>18</div></div><div><span><span>                </span></span><span>border: none;              /* 无边框 */</span></div></div><div><div><div>19</div></div><div><span><span>                </span></span><span>color: white;              /* 白色文字 */</span></div></div><div><div><div>20</div></div><div><span><span>                </span></span><span>padding: 15px 32px;        /* 内边距 */</span></div></div><div><div><div>21</div></div><div><span><span>                </span></span><span>text-align: center;        /* 文字居中 */</span></div></div><div><div><div>22</div></div><div><span><span>                </span></span><span>font-size: 16px;           /* 字体大小 */</span></div></div><div><div><div>23</div></div><div><span><span>                </span></span><span>border-radius: 8px;        /* 圆角 */</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span><span>            </span></span><span>/* 按钮悬停状态样式 */</span></div></div><div><div><div>27</div></div><div><span><span>            </span></span><span>QPushButton:hover {</span></div></div><div><div><div>28</div></div><div><span><span>                </span></span><span>background-color: #45a049;</span></div></div><div><div><div>29</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span><span>            </span></span><span>/* 按钮按下状态样式 */</span></div></div><div><div><div>32</div></div><div><span><span>            </span></span><span>QPushButton:pressed {</span></div></div><div><div><div>33</div></div><div><span><span>                </span></span><span>background-color: #3e8e41;</span></div></div><div><div><div>34</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>"""</span><span>)</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>        </span><span># 设置固定大小策略</span></div></div><div><div><div>38</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setSizePolicy</span><span>(</span></span></div></div><div><div><div>39</div></div><div><span><span>            </span></span><span>QSizePolicy.Preferred,  </span><span># 水平策略</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>QSizePolicy.Fixed       </span><span># 垂直固定高度</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span># 使用示例</span></div></div><div><div><div>44</div></div><div><span><span>button </span><span>=</span><span> </span><span>CustomButton</span><span>(</span></span><span>"点击我"</span><span>)</span></div></div><div><div><div>45</div></div><div><span><span>button.</span><span>show</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>自定义组件最佳实践：</strong></p><ol>
<li>
<p><strong>继承选择</strong>：</p>
<ul>
<li>从最接近需求的现有组件继承</li>
<li>常用基类：QWidget、QFrame、QPushButton等</li>
</ul>
</li>
<li>
<p><strong>样式控制</strong>：</p>
<ul>
<li>使用Qt样式表(QSS)定制外观</li>
<li>支持状态伪类(, )</li>
<li>保持样式与业务逻辑分离</li>
</ul>
</li>
<li>
<p><strong>功能扩展</strong>：</p>
<ul>
<li>添加自定义方法和属性</li>
<li>重写事件处理函数(如mousePressEvent)</li>
<li>定义新信号用于组件间通信</li>
</ul>
</li>
</ol></section><section><h3>7. 事件处理机制<a href="#7-事件处理机制"><span>#</span></a></h3><p>PySide6的事件系统处理所有用户交互和系统事件，采用事件循环机制：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QWidget, QPushButton</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> Qt  </span><span># 导入Qt键值常量</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>EventHandlingWindow</span><span>(</span><span>QWidget</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>事件处理演示窗口</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>展示：</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>- 按钮点击事件(信号与槽)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>- 键盘事件处理</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>        </span><span># 窗口基本设置</span></div></div><div><div><div>16</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"事件处理演示"</span><span>)</span></div></div><div><div><div>17</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>300</span><span>, </span><span>200</span><span>)  </span><span># x,y,width,height</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>        </span><span># 创建按钮并连接信号</span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>button </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"点击我"</span><span>, </span><span>self</span><span>)</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>button.</span><span>move</span><span>(</span><span>100</span><span>, </span><span>80</span><span>)  </span><span># 设置按钮位置</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>button.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.on_button_click)  </span><span># 连接点击信号</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>    </span><span>def</span><span> </span><span>on_button_click</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>25</div></div><div><span>        </span><span>"""按钮点击事件处理函数"""</span></div></div><div><div><div>26</div></div><div><span>        </span><span>print</span><span>(</span><span>"按钮被点击!"</span><span>)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>    </span><span>def</span><span> </span><span>keyPressEvent</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>event</span></span><span>):</span></div></div><div><div><div>29</div></div><div><span>        </span><span>"""键盘事件处理函数"""</span></div></div><div><div><div>30</div></div><div><span>        </span><span>if</span><span><span> event.</span><span>key</span><span>() </span><span>==</span><span> Qt.Key_Escape:  </span></span><span># ESC键</span></div></div><div><div><div>31</div></div><div><span>            </span><span>self</span><span><span>.</span><span>close</span><span>()  </span></span><span># 关闭窗口</span></div></div><div><div><div>32</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>33</div></div><div><span>            </span><span># 打印按键信息</span></div></div><div><div><div>34</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"按键: </span><span>{</span><span><span>event.</span><span>text</span><span>()</span></span><span>}</span><span> (Key code: </span><span>{</span><span><span>event.</span><span>key</span><span>()</span></span><span>}</span><span>)"</span><span>)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span># 应用程序入口</span></div></div><div><div><div>37</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)  </span><span># 创建应用实例</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>EventHandlingWindow</span><span>()  </span><span># 创建窗口实例</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()  </span><span># 显示窗口</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>exec</span><span>())  </span><span># 启动事件循环</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>事件处理要点：</strong></p><ol>
<li>
<p><strong>事件循环</strong>：</p>
<ul>
<li><code>app.exec()</code>启动主事件循环</li>
<li>持续监听和分发事件(鼠标、键盘、定时器等)</li>
</ul>
</li>
<li>
<p><strong>事件类型</strong>：</p>
<ul>
<li>鼠标事件：<code>mousePressEvent</code>, <code>mouseMoveEvent</code>等</li>
<li>键盘事件：<code>keyPressEvent</code>, <code>keyReleaseEvent</code></li>
<li>窗口事件：<code>closeEvent</code>, <code>resizeEvent</code>等</li>
</ul>
</li>
<li>
<p><strong>事件处理流程</strong>：</p>
<ul>
<li>事件首先发送到目标组件</li>
<li>如果未处理，会传递给父组件</li>
<li>可通过<code>event.accept()</code>或<code>event.ignore()</code>控制事件传播</li>
</ul>
</li>
</ol></section><section><h3>8. 对话框使用指南<a href="#8-对话框使用指南"><span>#</span></a></h3><p>PySide6提供多种标准对话框，用于用户交互：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> (QApplication, QMainWindow, QPushButton,</span></div></div><div><div><div>3</div></div><div><span><span>                              </span></span><span>QMessageBox, QInputDialog, QVBoxLayout, QWidget)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>class</span><span><span> </span><span>DialogDemoWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>对话框演示窗口</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>展示：</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>- 信息提示框(QMessageBox)</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>- 输入对话框(QInputDialog)</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>12</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>13</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>14</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"对话框演示"</span><span>)</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>400</span><span>, </span><span>300</span><span>)</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>        </span><span># 使用布局管理器</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>        </span><span># 信息提示框按钮</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>btn_msg </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"显示消息框"</span><span>)</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>btn_msg.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.show_message_box)</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_msg)</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>        </span><span># 输入对话框按钮</span></div></div><div><div><div>28</div></div><div><span><span>        </span></span><span>btn_input </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"显示输入框"</span><span>)</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>btn_input.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.show_input_dialog)</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_input)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>def</span><span> </span><span>show_message_box</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>33</div></div><div><span>        </span><span>"""显示标准信息提示框"""</span></div></div><div><div><div>34</div></div><div><span>        </span><span># 参数：父窗口, 标题, 消息内容</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>QMessageBox.</span><span>information</span><span>(</span></div></div><div><div><div>36</div></div><div><span>            </span><span>self</span><span>,</span></div></div><div><div><div>37</div></div><div><span>            </span><span>"提示"</span><span>,</span></div></div><div><div><div>38</div></div><div><span>            </span><span>"这是一个信息提示框</span><span>\n</span><span>支持多行文本显示"</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>    </span><span>def</span><span> </span><span>show_input_dialog</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>42</div></div><div><span>        </span><span>"""显示文本输入对话框"""</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>text, ok </span><span>=</span><span> QInputDialog.</span><span>getText</span><span>(</span></div></div><div><div><div>44</div></div><div><span>            </span><span>self</span><span>,</span></div></div><div><div><div>45</div></div><div><span>            </span><span>"输入对话框"</span><span>,</span></div></div><div><div><div>46</div></div><div><span>            </span><span>"请输入您的名字:"</span><span>,</span></div></div><div><div><div>47</div></div><div><span>            </span><span>echo</span><span><span>=</span><span>QLineEdit.Normal  </span></span><span># 正常显示输入</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>        </span><span>if</span><span> ok </span><span>and</span><span> text:  </span><span># 用户点击OK且有输入内容</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>QMessageBox.</span><span>information</span><span>(</span></div></div><div><div><div>52</div></div><div><span>                </span><span>self</span><span>,</span></div></div><div><div><div>53</div></div><div><span>                </span><span>"欢迎"</span><span>,</span></div></div><div><div><div>54</div></div><div><span>                </span><span>f</span><span>"您好, </span><span>{</span><span>text</span><span>}</span><span>!"</span></div></div><div><div><div>55</div></div><div><span><span>            </span></span><span>)</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span># 应用程序入口</span></div></div><div><div><div>58</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>DialogDemoWindow</span><span>()</span></div></div><div><div><div>61</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>exec</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>对话框类型及用途：</strong></p><ol>
<li>
<p><strong>QMessageBox</strong>：</p>
<ul>
<li><code>.information()</code> 信息提示</li>
<li><code>.warning()</code> 警告提示</li>
<li><code>.critical()</code> 错误提示</li>
<li><code>.question()</code> 确认对话框</li>
</ul>
</li>
<li>
<p><strong>QInputDialog</strong>：</p>
<ul>
<li><code>.getText()</code> 获取文本</li>
<li><code>.getInt()</code> 获取整数</li>
<li><code>.getDouble()</code> 获取浮点数</li>
<li><code>.getItem()</code> 选择列表项</li>
</ul>
</li>
<li>
<p><strong>模态特性</strong>：</p>
<ul>
<li>模态对话框会阻塞父窗口</li>
<li>非模态对话框允许同时操作父窗口</li>
<li>使用<code>setModal(True)</code>设置模态</li>
</ul>
</li>
</ol></section></section><section><h2>文件操作<a href="#文件操作"><span>#</span></a></h2><p>在 PySide6 应用程序中进行文件操作，通常会结合 <code>QFileDialog</code> 来选择文件路径。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 文件操作示例：创建一个简单的文本编辑器，支持打开和保存文件</span></div></div><div><div><div>2</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> (QApplication, QMainWindow, QPushButton,</span></div></div><div><div><div>4</div></div><div><span><span>                              </span></span><span>QTextEdit, QVBoxLayout, QWidget, QFileDialog,</span></div></div><div><div><div>5</div></div><div><span><span>                              </span></span><span>QMessageBox)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span><span> </span><span>FileOperationWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>文件操作演示窗口类</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>功能：</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>- 打开文本文件并显示内容</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>- 编辑文本内容并保存到文件</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>14</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>15</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>16</div></div><div><span>        </span><span># 初始化窗口属性</span></div></div><div><div><div>17</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"文件操作演示"</span><span>)</span></div></div><div><div><div>18</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>500</span><span>, </span><span>400</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>        </span><span># 创建中心部件和布局</span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>22</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span>        </span><span># 创建文本编辑区域</span></div></div><div><div><div>26</div></div><div><span>        </span><span>self</span><span><span>.text_edit </span><span>=</span><span> </span><span>QTextEdit</span><span>()</span></span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.text_edit)</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span>        </span><span># 创建打开文件按钮</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>btn_open </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"打开文件"</span><span>)</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>btn_open.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.open_file)  </span><span># 连接点击信号到槽函数</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_open)</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>        </span><span># 创建保存文件按钮</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>btn_save </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"保存文件"</span><span>)</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>btn_save.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.save_file)  </span><span># 连接点击信号到槽函数</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_save)</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>    </span><span>def</span><span> </span><span>open_file</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>40</div></div><div><span>        </span><span>"""打开文件并读取内容到文本编辑器"""</span></div></div><div><div><div>41</div></div><div><span>        </span><span># 显示文件打开对话框，限制文件类型为文本文件</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>file_name, _ </span><span>=</span><span> QFileDialog.</span><span>getOpenFileName</span><span>(</span></div></div><div><div><div>43</div></div><div><span>            </span><span>self</span><span>, </span><span>"打开文件"</span><span>, </span><span>""</span><span>, </span><span>"文本文件 (*.txt);;所有文件 (*)"</span><span>)</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>        </span><span>if</span><span> file_name:  </span><span># 如果用户选择了文件</span></div></div><div><div><div>46</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>47</div></div><div><span>                </span><span># 使用utf-8编码打开文件并读取内容</span></div></div><div><div><div>48</div></div><div><span>                </span><span>with</span><span> </span><span>open</span><span>(file_name, </span><span>'r'</span><span>, </span><span>encoding</span><span>=</span><span>'utf-8'</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>49</div></div><div><span><span>                    </span></span><span>content </span><span>=</span><span> f.</span><span>read</span><span>()</span></div></div><div><div><div>50</div></div><div><span>                    </span><span>self</span><span><span>.text_edit.</span><span>setText</span><span>(content)  </span></span><span># 显示文件内容</span></div></div><div><div><div>51</div></div><div><span>            </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>52</div></div><div><span>                </span><span># 捕获并显示文件打开错误</span></div></div><div><div><div>53</div></div><div><span><span>                </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>f</span><span>"无法打开文件: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>    </span><span>def</span><span> </span><span>save_file</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>56</div></div><div><span>        </span><span>"""将文本编辑器内容保存到文件"""</span></div></div><div><div><div>57</div></div><div><span>        </span><span># 显示文件保存对话框</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>file_name, _ </span><span>=</span><span> QFileDialog.</span><span>getSaveFileName</span><span>(</span></div></div><div><div><div>59</div></div><div><span>            </span><span>self</span><span>, </span><span>"保存文件"</span><span>, </span><span>""</span><span>, </span><span>"文本文件 (*.txt);;所有文件 (*)"</span><span>)</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>        </span><span>if</span><span> file_name:  </span><span># 如果用户指定了保存路径</span></div></div><div><div><div>62</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>63</div></div><div><span>                </span><span># 使用utf-8编码写入文件</span></div></div><div><div><div>64</div></div><div><span>                </span><span>with</span><span> </span><span>open</span><span>(file_name, </span><span>'w'</span><span>, </span><span>encoding</span><span>=</span><span>'utf-8'</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>65</div></div><div><span><span>                    </span></span><span>f.</span><span>write</span><span>(</span><span>self</span><span><span>.text_edit.</span><span>toPlainText</span><span>())  </span></span><span># 获取纯文本内容并保存</span></div></div><div><div><div>66</div></div><div><span>            </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>67</div></div><div><span>                </span><span># 捕获并显示文件保存错误</span></div></div><div><div><div>68</div></div><div><span><span>                </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>f</span><span>"无法保存文件: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span># 创建并运行应用程序</span></div></div><div><div><div>71</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>72</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>FileOperationWindow</span><span>()</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>app.</span><span>exec</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>文件操作详解<a href="#文件操作详解"><span>#</span></a></h3><p>PySide6 提供了 <code>QFileDialog</code> 类来处理文件选择操作，这是 GUI 应用中常见的功能。下面详细介绍文件操作的关键点：</p><ol>
<li>
<p><strong>文件对话框类型</strong></p>
<ul>
<li><code>getOpenFileName()</code>: 用于选择要打开的文件</li>
<li><code>getSaveFileName()</code>: 用于选择要保存的文件路径</li>
<li><code>getExistingDirectory()</code>: 用于选择目录</li>
</ul>
</li>
<li>
<p><strong>文件过滤器</strong>
可以通过字符串参数指定文件类型过滤器，例如：</p>
<ul>
<li><code>"Text Files (*.txt)"</code>: 只显示.txt文件</li>
<li><code>"All Files (*)"</code>: 显示所有文件</li>
<li>多个过滤器用<code>;;</code>分隔</li>
</ul>
</li>
<li>
<p><strong>安全文件操作</strong>
使用Python的<code>with</code>语句可以确保文件正确关闭，即使在发生异常的情况下。</p>
</li>
</ol></section></section><section><h2>多线程<a href="#多线程"><span>#</span></a></h2><p>在 GUI 应用程序中，长时间运行的任务应该在单独的线程中执行，以避免阻塞主线程，保持界面响应。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 多线程示例：演示如何在GUI应用中使用工作线程</span></div></div><div><div><div>2</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>3</div></div><div><span>import</span><span> time</span></div></div><div><div><div>4</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> (QApplication, QMainWindow, QPushButton,</span></div></div><div><div><div>5</div></div><div><span><span>                              </span></span><span>QLabel, QVBoxLayout, QWidget)</span></div></div><div><div><div>6</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> QThread, Signal</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>class</span><span><span> </span><span>Worker</span><span>(</span><span>QThread</span><span>)</span></span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>工作线程类</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>功能：在后台执行耗时任务，完成后通过信号通知主线程</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>13</div></div><div><span>    </span><span># 定义信号</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>progress_updated </span><span>=</span><span> </span><span>Signal</span><span>(</span><span>int</span><span>)  </span><span># 进度更新信号</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>finished </span><span>=</span><span> </span><span>Signal</span><span>(</span><span>str</span><span>)         </span><span># 任务完成信号</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>error_occurred </span><span>=</span><span> </span><span>Signal</span><span>(</span><span>str</span><span>)   </span><span># 错误发生信号</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>    </span><span>def</span><span> </span><span>run</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>19</div></div><div><span>        </span><span>"""线程执行的主方法"""</span></div></div><div><div><div>20</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>21</div></div><div><span>            </span><span># 模拟耗时操作（如网络请求、复杂计算等）</span></div></div><div><div><div>22</div></div><div><span>            </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>1</span><span>, </span><span>101</span><span>):</span></div></div><div><div><div>23</div></div><div><span><span>                </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.05</span><span>)  </span><span># 模拟工作进度</span></div></div><div><div><div>24</div></div><div><span>                </span><span>self</span><span><span>.progress_updated.</span><span>emit</span><span>(i)  </span></span><span># 发送进度更新信号</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>            </span><span># 任务完成后发出信号，传递完成消息</span></div></div><div><div><div>27</div></div><div><span>            </span><span>self</span><span><span>.finished.</span><span>emit</span><span>(</span></span><span>"任务成功完成!"</span><span>)</span></div></div><div><div><div>28</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>29</div></div><div><span>            </span><span># 捕获异常并通过信号通知主线程</span></div></div><div><div><div>30</div></div><div><span>            </span><span>self</span><span><span>.error_occurred.</span><span>emit</span><span>(</span></span><span>f</span><span>"任务失败: </span><span>{</span><span>str</span><span>(e)</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>class</span><span><span> </span><span>MultiThreadingDemo</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>33</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>多线程演示窗口类</span></div></div><div><div><div>35</div></div><div><span><span>    </span></span><span>功能：</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>- 启动后台任务</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>- 显示任务状态和进度</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>- 处理任务完成和错误通知</span></div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>40</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>41</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>42</div></div><div><span>        </span><span># 初始化窗口属性</span></div></div><div><div><div>43</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"多线程演示"</span><span>)</span></div></div><div><div><div>44</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>400</span><span>, </span><span>250</span><span>)</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>        </span><span># 创建中心部件和布局</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>48</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span>        </span><span># 创建状态标签</span></div></div><div><div><div>52</div></div><div><span>        </span><span>self</span><span><span>.status_label </span><span>=</span><span> </span><span>QLabel</span><span>(</span></span><span>"准备就绪"</span><span>)</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.status_label)</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>        </span><span># 创建进度标签</span></div></div><div><div><div>56</div></div><div><span>        </span><span>self</span><span><span>.progress_label </span><span>=</span><span> </span><span>QLabel</span><span>(</span></span><span>"进度: 0%"</span><span>)</span></div></div><div><div><div>57</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.progress_label)</span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span>        </span><span># 创建启动任务按钮</span></div></div><div><div><div>60</div></div><div><span><span>        </span></span><span>btn_start </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"开始长时间任务"</span><span>)</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>btn_start.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.start_long_task)  </span><span># 连接按钮点击信号</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_start)</span></div></div><div><div><div>63</div></div><div>
</div></div><div><div><div>64</div></div><div><span>        </span><span># 创建停止任务按钮</span></div></div><div><div><div>65</div></div><div><span><span>        </span></span><span>btn_stop </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"停止任务"</span><span>)</span></div></div><div><div><div>66</div></div><div><span><span>        </span></span><span>btn_stop.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.stop_long_task)  </span><span># 连接按钮点击信号</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>btn_stop.</span><span>setEnabled</span><span>(</span><span>False</span><span>)  </span><span># 初始不可用</span></div></div><div><div><div>68</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_stop)</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span>        </span><span># 线程状态标志</span></div></div><div><div><div>71</div></div><div><span>        </span><span>self</span><span><span>.is_running </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>    </span><span>def</span><span> </span><span>start_long_task</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>74</div></div><div><span>        </span><span>"""启动后台任务"""</span></div></div><div><div><div>75</div></div><div><span>        </span><span>if</span><span> </span><span>self</span><span>.is_running:</span></div></div><div><div><div>76</div></div><div><span>            </span><span>return</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>        </span><span>self</span><span><span>.is_running </span><span>=</span><span> </span></span><span>True</span></div></div><div><div><div>79</div></div><div><span>        </span><span>self</span><span><span>.status_label.</span><span>setText</span><span>(</span></span><span>"任务运行中..."</span><span>)</span></div></div><div><div><div>80</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"开始长时间任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>False</span><span>)</span></div></div><div><div><div>81</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"停止任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>True</span><span>)</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span>        </span><span># 创建工作线程实例</span></div></div><div><div><div>84</div></div><div><span>        </span><span>self</span><span><span>.worker_thread </span><span>=</span><span> </span><span>Worker</span><span>()</span></span></div></div><div><div><div>85</div></div><div><span>        </span><span># 连接各种信号到处理函数</span></div></div><div><div><div>86</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.progress_updated.</span><span>connect</span><span>(</span></span><span>self</span><span>.update_progress)</span></div></div><div><div><div>87</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.finished.</span><span>connect</span><span>(</span></span><span>self</span><span>.on_task_finished)</span></div></div><div><div><div>88</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.error_occurred.</span><span>connect</span><span>(</span></span><span>self</span><span>.on_task_error)</span></div></div><div><div><div>89</div></div><div><span>        </span><span># 启动线程</span></div></div><div><div><div>90</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.</span><span>start</span><span>()</span></span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span>    </span><span>def</span><span> </span><span>stop_long_task</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>93</div></div><div><span>        </span><span>"""停止后台任务"""</span></div></div><div><div><div>94</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> </span><span>self</span><span>.is_running:</span></div></div><div><div><div>95</div></div><div><span>            </span><span>return</span></div></div><div><div><div>96</div></div><div>
</div></div><div><div><div>97</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.</span><span>terminate</span><span>()  </span></span><span># 终止线程</span></div></div><div><div><div>98</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.</span><span>wait</span><span>()       </span></span><span># 等待线程结束</span></div></div><div><div><div>99</div></div><div><span>        </span><span>self</span><span><span>.status_label.</span><span>setText</span><span>(</span></span><span>"任务已停止"</span><span>)</span></div></div><div><div><div>100</div></div><div><span>        </span><span>self</span><span><span>.is_running </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>101</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"开始长时间任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>True</span><span>)</span></div></div><div><div><div>102</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"停止任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>False</span><span>)</span></div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div><span>    </span><span>def</span><span> </span><span>update_progress</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>value</span></span><span>):</span></div></div><div><div><div>105</div></div><div><span>        </span><span>"""更新进度显示"""</span></div></div><div><div><div>106</div></div><div><span>        </span><span>self</span><span><span>.progress_label.</span><span>setText</span><span>(</span></span><span>f</span><span>"进度: </span><span>{</span><span>value</span><span>}</span><span>%"</span><span>)</span></div></div><div><div><div>107</div></div><div>
</div></div><div><div><div>108</div></div><div><span>    </span><span>def</span><span> </span><span>on_task_finished</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>message</span></span><span>):</span></div></div><div><div><div>109</div></div><div><span>        </span><span>"""任务完成处理函数"""</span></div></div><div><div><div>110</div></div><div><span>        </span><span>self</span><span><span>.status_label.</span><span>setText</span><span>(message)</span></span></div></div><div><div><div>111</div></div><div><span>        </span><span>self</span><span><span>.progress_label.</span><span>setText</span><span>(</span></span><span>"进度: 100%"</span><span>)</span></div></div><div><div><div>112</div></div><div><span>        </span><span>self</span><span><span>.is_running </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>113</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"开始长时间任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>True</span><span>)</span></div></div><div><div><div>114</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"停止任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>False</span><span>)</span></div></div><div><div><div>115</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.</span><span>deleteLater</span><span>()  </span></span><span># 安全释放线程资源</span></div></div><div><div><div>116</div></div><div>
</div></div><div><div><div>117</div></div><div><span>    </span><span>def</span><span> </span><span>on_task_error</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>error_msg</span></span><span>):</span></div></div><div><div><div>118</div></div><div><span>        </span><span>"""任务错误处理函数"""</span></div></div><div><div><div>119</div></div><div><span>        </span><span>self</span><span><span>.status_label.</span><span>setText</span><span>(error_msg)</span></span></div></div><div><div><div>120</div></div><div><span>        </span><span>self</span><span><span>.is_running </span><span>=</span><span> </span></span><span>False</span></div></div><div><div><div>121</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"开始长时间任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>True</span><span>)</span></div></div><div><div><div>122</div></div><div><span>        </span><span>self</span><span><span>.</span><span>findChild</span><span>(QPushButton, </span></span><span>"停止任务"</span><span><span>).</span><span>setEnabled</span><span>(</span></span><span>False</span><span>)</span></div></div><div><div><div>123</div></div><div><span>        </span><span>self</span><span><span>.worker_thread.</span><span>deleteLater</span><span>()  </span></span><span># 安全释放线程资源</span></div></div><div><div><div>124</div></div><div>
</div></div><div><div><div>125</div></div><div><span># 创建并运行应用程序</span></div></div><div><div><div>126</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>127</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>128</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>MultiThreadingDemo</span><span>()</span></div></div><div><div><div>129</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>app.</span><span>exec</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>多线程编程指南<a href="#多线程编程指南"><span>#</span></a></h3><p>在GUI应用中，长时间运行的任务必须放在单独的线程中执行，以避免界面冻结。PySide6使用<code>QThread</code>和<code>Signal</code>机制实现线程间通信。</p></section><section><h3>关键注意事项<a href="#关键注意事项"><span>#</span></a></h3><ol>
<li>
<p><strong>线程安全</strong></p>
<ul>
<li>GUI操作只能在主线程执行</li>
<li>工作线程不能直接操作UI组件</li>
<li>通过Signal/Slot机制进行跨线程通信</li>
</ul>
</li>
<li>
<p><strong>线程生命周期</strong></p>
<ul>
<li>使用<code>start()</code>方法启动线程</li>
<li><code>finished</code>信号在线程结束时发出</li>
<li>确保线程对象在适当时候被销毁</li>
</ul>
</li>
<li>
<p><strong>错误处理</strong></p>
<ul>
<li>捕获工作线程中的异常</li>
<li>通过信号将错误信息传递到主线程</li>
<li>在主线程中显示错误信息</li>
</ul>
</li>
</ol></section></section><section><h2>数据库集成 *<a href="#数据库集成-"><span>#</span></a></h2><p>PySide6 可以与各种数据库进行集成，例如 SQLite，通过 Qt 的数据库模块 <code>QtSql</code>。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QMessageBox</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtSql </span><span>import</span><span> QSqlDatabase, QSqlQuery</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 数据库集成演示窗口</span></div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>DatabaseDemoWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>9</div></div><div><span>        </span><span># 设置窗口标题和大小</span></div></div><div><div><div>10</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"数据库集成演示"</span><span>)</span></div></div><div><div><div>11</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>600</span><span>, </span><span>400</span><span>)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>        </span><span># 创建中央部件和布局</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>        </span><span># 创建表格部件用于显示数据</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.table_widget </span><span>=</span><span> </span><span>QTableWidget</span><span>()</span></span></div></div><div><div><div>20</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.table_widget)</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>        </span><span># 添加加载数据按钮</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>btn_load </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"加载数据"</span><span>)</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>btn_load.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.load_data)</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_load)</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>        </span><span># 初始化数据库连接</span></div></div><div><div><div>28</div></div><div><span>        </span><span>self</span><span><span>.</span><span>init_database</span><span>()</span></span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>    </span><span>def</span><span> </span><span>init_database</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>31</div></div><div><span>        </span><span># 添加SQLite数据库驱动</span></div></div><div><div><div>32</div></div><div><span>        </span><span>self</span><span><span>.db </span><span>=</span><span> QSqlDatabase.</span><span>addDatabase</span><span>(</span></span><span>"QSQLITE"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>        </span><span># 设置数据库文件名</span></div></div><div><div><div>34</div></div><div><span>        </span><span>self</span><span><span>.db.</span><span>setDatabaseName</span><span>(</span></span><span>"example.db"</span><span>)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>        </span><span># 尝试打开数据库</span></div></div><div><div><div>37</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> </span><span>self</span><span><span>.db.</span><span>open</span><span>():</span></span></div></div><div><div><div>38</div></div><div><span><span>            </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>"无法打开数据库"</span><span>)</span></div></div><div><div><div>39</div></div><div><span>            </span><span>return</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>        </span><span># 创建表和初始化数据</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>query </span><span>=</span><span> </span><span>QSqlQuery</span><span>()</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>query.</span><span>exec</span><span>(</span><span>"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"</span><span>)</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>query.</span><span>exec</span><span>(</span><span>"INSERT OR IGNORE INTO users (id, name, age) VALUES (1, '张三', 30)"</span><span>)</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>query.</span><span>exec</span><span>(</span><span>"INSERT OR IGNORE INTO users (id, name, age) VALUES (2, '李四', 24)"</span><span>)</span></div></div><div><div><div>46</div></div><div><span><span>        </span></span><span>query.</span><span>exec</span><span>(</span><span>"INSERT OR IGNORE INTO users (id, name, age) VALUES (3, '王五', 35)"</span><span>)</span></div></div><div><div><div>47</div></div><div>
</div></div><div><div><div>48</div></div><div><span>    </span><span>def</span><span> </span><span>load_data</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>49</div></div><div><span>        </span><span># 执行SQL查询获取用户数据</span></div></div><div><div><div>50</div></div><div><span><span>        </span></span><span>query </span><span>=</span><span> </span><span>QSqlQuery</span><span>(</span><span>"SELECT name, age FROM users"</span><span>)</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>        </span><span># 初始化表格</span></div></div><div><div><div>53</div></div><div><span>        </span><span>self</span><span><span>.table_widget.</span><span>setRowCount</span><span>(</span></span><span>0</span><span>)</span></div></div><div><div><div>54</div></div><div><span>        </span><span>self</span><span><span>.table_widget.</span><span>setColumnCount</span><span>(</span></span><span>2</span><span>)</span></div></div><div><div><div>55</div></div><div><span>        </span><span>self</span><span><span>.table_widget.</span><span>setHorizontalHeaderLabels</span><span>([</span></span><span>"姓名"</span><span>, </span><span>"年龄"</span><span>])</span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span>        </span><span># 遍历查询结果并填充表格</span></div></div><div><div><div>58</div></div><div><span><span>        </span></span><span>row </span><span>=</span><span> </span><span>0</span></div></div><div><div><div>59</div></div><div><span>        </span><span>while</span><span><span> query.</span><span>next</span><span>():</span></span></div></div><div><div><div>60</div></div><div><span>            </span><span>self</span><span><span>.table_widget.</span><span>insertRow</span><span>(row)</span></span></div></div><div><div><div>61</div></div><div><span>            </span><span>self</span><span><span>.table_widget.</span><span>setItem</span><span>(row, </span></span><span>0</span><span><span>, </span><span>QTableWidgetItem</span><span>(query.</span><span>value</span><span>(</span></span><span>0</span><span>)))</span></div></div><div><div><div>62</div></div><div><span>            </span><span>self</span><span><span>.table_widget.</span><span>setItem</span><span>(row, </span></span><span>1</span><span><span>, </span><span>QTableWidgetItem</span><span>(</span></span><span>str</span><span><span>(query.</span><span>value</span><span>(</span></span><span>1</span><span>))))</span></div></div><div><div><div>63</div></div><div><span><span>            </span></span><span>row </span><span>+=</span><span> </span><span>1</span></div></div><div><div><div>64</div></div><div>
</div></div><div><div><div>65</div></div><div><span>    </span><span>def</span><span> </span><span>closeEvent</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>event</span></span><span>):</span></div></div><div><div><div>66</div></div><div><span>        </span><span># 关闭窗口时断开数据库连接</span></div></div><div><div><div>67</div></div><div><span>        </span><span>self</span><span><span>.db.</span><span>close</span><span>()</span></span></div></div><div><div><div>68</div></div><div><span>        </span><span>super</span><span><span>().</span><span>closeEvent</span><span>(event)</span></span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span># 应用程序入口</span></div></div><div><div><div>71</div></div><div><span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></span></div></div><div><div><div>72</div></div><div><span><span>window </span><span>=</span><span> </span><span>DatabaseDemoWindow</span><span>()</span></span></div></div><div><div><div>73</div></div><div><span><span>window.</span><span>show</span><span>()</span></span></div></div><div><div><div>74</div></div><div><span><span>app.</span><span>exec</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>数据库集成详解 *<a href="#数据库集成详解-"><span>#</span></a></h3><p>PySide6 通过 <code>QtSql</code> 模块提供了强大的数据库支持，可以轻松集成 SQLite、MySQL、PostgreSQL 等数据库。</p></section><section><h3>数据库连接管理 *<a href="#数据库连接管理-"><span>#</span></a></h3><ol>
<li>
<p><strong>连接数据库</strong></p>
<ul>
<li>使用 <code>QSqlDatabase.addDatabase()</code> 创建数据库连接</li>
<li>通过 <code>setDatabaseName()</code> 设置数据库名称或路径</li>
<li>调用 <code>open()</code> 方法建立连接</li>
</ul>
</li>
<li>
<p><strong>执行SQL查询</strong></p>
<ul>
<li><code>QSqlQuery</code> 用于执行SQL语句</li>
<li><code>exec()</code> 方法执行不返回结果的查询</li>
<li><code>next()</code> 方法遍历结果集</li>
</ul>
</li>
<li>
<p><strong>数据显示</strong></p>
<ul>
<li><code>QTableWidget</code> 适合显示表格数据</li>
<li>使用 <code>setRowCount()</code> 和 <code>setColumnCount()</code> 设置表格大小</li>
<li>通过 <code>setItem()</code> 填充表格内容</li>
</ul>
</li>
</ol></section></section><section><h2>网络编程 *<a href="#网络编程-"><span>#</span></a></h2><p>PySide6 提供了 <code>QtNetwork</code> 模块，用于处理网络通信，例如 HTTP 请求。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 导入必要的模块</span></div></div><div><div><div>2</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> (QApplication, QMainWindow, QPushButton,</span></div></div><div><div><div>4</div></div><div><span><span>                              </span></span><span>QTextEdit, QVBoxLayout, QWidget, QMessageBox)</span></div></div><div><div><div>5</div></div><div><span>from</span><span> PySide6.QtNetwork </span><span>import</span><span> QNetworkAccessManager, QNetworkRequest, QNetworkReply</span></div></div><div><div><div>6</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> QUrl</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>class</span><span><span> </span><span>NetworkDemoWindow</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>网络请求演示窗口</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>展示如何使用PySide6进行HTTP网络请求</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>13</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>14</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>15</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"网络请求演示"</span><span>)</span></div></div><div><div><div>16</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>600</span><span>, </span><span>400</span><span>)</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>        </span><span># 创建中央部件和布局</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>20</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>21</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>        </span><span># 创建文本显示区域</span></div></div><div><div><div>24</div></div><div><span>        </span><span>self</span><span><span>.text_edit </span><span>=</span><span> </span><span>QTextEdit</span><span>()</span></span></div></div><div><div><div>25</div></div><div><span>        </span><span>self</span><span><span>.text_edit.</span><span>setReadOnly</span><span>(</span></span><span>True</span><span>)  </span><span># 设置为只读</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.text_edit)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>        </span><span># 创建请求按钮</span></div></div><div><div><div>29</div></div><div><span><span>        </span></span><span>btn_fetch </span><span>=</span><span> </span><span>QPushButton</span><span>(</span><span>"获取网络数据"</span><span>)</span></div></div><div><div><div>30</div></div><div><span><span>        </span></span><span>btn_fetch.clicked.</span><span>connect</span><span>(</span><span>self</span><span>.fetch_data)</span></div></div><div><div><div>31</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(btn_fetch)</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>        </span><span># 初始化网络管理器</span></div></div><div><div><div>34</div></div><div><span>        </span><span>self</span><span><span>.manager </span><span>=</span><span> </span><span>QNetworkAccessManager</span><span>()</span></span></div></div><div><div><div>35</div></div><div><span>        </span><span># 连接请求完成信号</span></div></div><div><div><div>36</div></div><div><span>        </span><span>self</span><span><span>.manager.finished.</span><span>connect</span><span>(</span></span><span>self</span><span>.on_request_finished)</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span>def</span><span> </span><span>fetch_data</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>39</div></div><div><span>        </span><span>"""</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>发起网络请求</span></div></div><div><div><div>41</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>42</div></div><div><span>        </span><span># 创建URL对象 - 这里可以替换为任何有效的URL</span></div></div><div><div><div>43</div></div><div><span><span>        </span></span><span>url </span><span>=</span><span> </span><span>QUrl</span><span>(</span><span>"https://www.example.com"</span><span>)</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>        </span><span># 验证URL有效性</span></div></div><div><div><div>46</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span><span> url.</span><span>isValid</span><span>():</span></span></div></div><div><div><div>47</div></div><div><span><span>            </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>"无效的URL地址"</span><span>)</span></div></div><div><div><div>48</div></div><div><span>            </span><span>return</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span>        </span><span># 创建网络请求</span></div></div><div><div><div>51</div></div><div><span><span>        </span></span><span>request </span><span>=</span><span> </span><span>QNetworkRequest</span><span>(url)</span></div></div><div><div><div>52</div></div><div><span>        </span><span># 设置请求头（可选）</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>request.</span><span>setRawHeader</span><span>(</span><span>b</span><span>"User-Agent"</span><span>, </span><span>b</span><span>"PySide6 Network Client"</span><span>)</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>        </span><span># 发送GET请求</span></div></div><div><div><div>56</div></div><div><span>        </span><span>self</span><span><span>.manager.</span><span>get</span><span>(request)</span></span></div></div><div><div><div>57</div></div><div><span>        </span><span>self</span><span><span>.text_edit.</span><span>setText</span><span>(</span></span><span>"正在请求数据..."</span><span>)</span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span>    </span><span>def</span><span> </span><span>on_request_finished</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>reply</span></span><span>:</span><span> QNetworkReply</span><span>):</span></div></div><div><div><div>60</div></div><div><span>        </span><span>"""</span></div></div><div><div><div>61</div></div><div><span><span>        </span></span><span>请求完成回调</span></div></div><div><div><div>62</div></div><div><span><span>        </span></span><span>"""</span></div></div><div><div><div>63</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>64</div></div><div><span>            </span><span># 检查是否有错误</span></div></div><div><div><div>65</div></div><div><span>            </span><span>if</span><span><span> reply.</span><span>error</span><span>() </span><span>==</span><span> QNetworkReply.NetworkError.NoError:</span></span></div></div><div><div><div>66</div></div><div><span>                </span><span># 读取响应数据并解码</span></div></div><div><div><div>67</div></div><div><span><span>                </span></span><span>data </span><span>=</span><span> reply.</span><span>readAll</span><span>().</span><span>data</span><span>().</span><span>decode</span><span>(</span><span>"utf-8"</span><span>)</span></div></div><div><div><div>68</div></div><div><span>                </span><span>self</span><span><span>.text_edit.</span><span>setText</span><span>(data)</span></span></div></div><div><div><div>69</div></div><div><span>            </span><span>else</span><span>:</span></div></div><div><div><div>70</div></div><div><span>                </span><span># 显示错误信息</span></div></div><div><div><div>71</div></div><div><span>                </span><span>self</span><span><span>.text_edit.</span><span>setText</span><span>(</span></span><span>f</span><span>"请求错误: </span><span>{</span><span><span>reply.</span><span>errorString</span><span>()</span></span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>72</div></div><div><span>        </span><span>finally</span><span>:</span></div></div><div><div><div>73</div></div><div><span>            </span><span># 确保释放reply对象</span></div></div><div><div><div>74</div></div><div><span><span>            </span></span><span>reply.</span><span>deleteLater</span><span>()</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span># 应用程序入口</span></div></div><div><div><div>77</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>78</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>NetworkDemoWindow</span><span>()</span></div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>81</div></div><div><span><span>    </span></span><span>app.</span><span>exec</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>网络编程详解<a href="#网络编程详解"><span>#</span></a></h3><p>PySide6 通过 <code>QtNetwork</code> 模块提供了强大的网络功能，支持 HTTP、HTTPS、FTP 等协议。</p></section><section><h3>关键组件<a href="#关键组件"><span>#</span></a></h3><ol>
<li>
<p><strong>QNetworkAccessManager</strong></p>
<ul>
<li>负责发送网络请求和管理网络操作</li>
<li>处理请求队列和缓存</li>
<li>提供信号机制监听请求状态</li>
</ul>
</li>
<li>
<p><strong>QNetworkRequest</strong></p>
<ul>
<li>封装请求URL和头部信息</li>
<li>可以设置请求属性如超时时间</li>
<li>支持HTTPS安全连接</li>
</ul>
</li>
<li>
<p><strong>QNetworkReply</strong></p>
<ul>
<li>表示网络请求的响应</li>
<li>提供读取响应数据的方法</li>
<li>包含错误状态和元数据</li>
</ul>
</li>
</ol></section><section><h3>异步处理<a href="#异步处理"><span>#</span></a></h3><ul>
<li>网络请求默认是异步的</li>
<li>通过信号/槽机制获取结果</li>
<li>避免阻塞主线程保持UI响应</li>
</ul></section><section><h3>最佳实践<a href="#最佳实践"><span>#</span></a></h3><ul>
<li>添加适当的请求超时处理</li>
<li>实现进度指示器</li>
<li>处理SSL/TLS安全连接</li>
<li>考虑使用线程处理大量请求</li>
</ul></section></section><section><h2>绘图与图形视图<a href="#绘图与图形视图"><span>#</span></a></h2><p>PySide6 提供了强大的绘图功能，通过 <code>QPainter</code> 可以在任何 <code>QWidget</code> 上进行绘制。<code>QGraphicsView</code> 和 <code>QGraphicsScene</code> 则提供了一个用于管理大量 2D 图形项的框架。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsEllipseItem</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtGui </span><span>import</span><span> QPainter, QColor, QPen</span></div></div><div><div><div>4</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> Qt, QRectF</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>CustomPaintWidget</span><span>(</span><span>QWidget</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>paintEvent</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>event</span></span><span>):</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>painter </span><span>=</span><span> </span><span>QPainter</span><span>(</span><span>self</span><span>)</span></div></div><div><div><div>9</div></div><div><span><span>        </span></span><span>painter.</span><span>setRenderHint</span><span>(QPainter.Antialiasing)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>        </span><span># 绘制矩形</span></div></div><div><div><div>12</div></div><div><span><span>        </span></span><span>painter.</span><span>setPen</span><span>(</span><span>QPen</span><span>(</span><span>QColor</span><span>(</span><span>255</span><span>, </span><span>0</span><span>, </span><span>0</span><span>), </span><span>2</span><span>)) </span><span># 红色边框</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>painter.</span><span>setBrush</span><span>(</span><span>QColor</span><span>(</span><span>255</span><span>, </span><span>0</span><span>, </span><span>0</span><span>, </span><span>127</span><span>)) </span><span># 半透明红色填充</span></div></div><div><div><div>14</div></div><div><span><span>        </span></span><span>painter.</span><span>drawRect</span><span>(</span><span>50</span><span>, </span><span>50</span><span>, </span><span>100</span><span>, </span><span>50</span><span>)</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>        </span><span># 绘制圆形</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>painter.</span><span>setPen</span><span>(</span><span>QPen</span><span>(</span><span>QColor</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>255</span><span>), </span><span>3</span><span>)) </span><span># 蓝色边框</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>painter.</span><span>setBrush</span><span>(</span><span>QColor</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>255</span><span>, </span><span>127</span><span>)) </span><span># 半透明蓝色填充</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>painter.</span><span>drawEllipse</span><span>(</span><span>150</span><span>, </span><span>100</span><span>, </span><span>80</span><span>, </span><span>80</span><span>)</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>        </span><span># 绘制文本</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>painter.</span><span>setPen</span><span>(</span><span>QPen</span><span>(</span><span>QColor</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>0</span><span>))) </span><span># 黑色文本</span></div></div><div><div><div>23</div></div><div><span><span>        </span></span><span>painter.</span><span>setFont</span><span>(painter.</span><span>font</span><span>().</span><span>setPointSize</span><span>(</span><span>16</span><span>))</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>painter.</span><span>drawText</span><span>(</span><span>50</span><span>, </span><span>200</span><span>, </span><span>"Hello PySide6 Drawing!"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>class</span><span><span> </span><span>GraphicsViewDemo</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>27</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>28</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>29</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>"Graphics View Demo"</span><span>)</span></div></div><div><div><div>30</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setGeometry</span><span>(</span></span><span>100</span><span>, </span><span>100</span><span>, </span><span>600</span><span>, </span><span>500</span><span>)</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>central_widget </span><span>=</span><span> </span><span>QWidget</span><span>()</span></div></div><div><div><div>33</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setCentralWidget</span><span>(central_widget)</span></span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>layout </span><span>=</span><span> </span><span>QVBoxLayout</span><span>(central_widget)</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span>        </span><span># 自定义绘图组件</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>paint_widget </span><span>=</span><span> </span><span>CustomPaintWidget</span><span>()</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>paint_widget.</span><span>setFixedSize</span><span>(</span><span>300</span><span>, </span><span>250</span><span>)</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(paint_widget)</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>        </span><span># 图形视图组件</span></div></div><div><div><div>42</div></div><div><span>        </span><span>self</span><span><span>.scene </span><span>=</span><span> </span><span>QGraphicsScene</span><span>(</span></span><span>self</span><span>)</span></div></div><div><div><div>43</div></div><div><span>        </span><span>self</span><span><span>.view </span><span>=</span><span> </span><span>QGraphicsView</span><span>(</span></span><span>self</span><span>.scene)</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>layout.</span><span>addWidget</span><span>(</span><span>self</span><span>.view)</span></div></div><div><div><div>45</div></div><div>
</div></div><div><div><div>46</div></div><div><span>        </span><span># 在场景中添加图形项</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>rect_item </span><span>=</span><span> </span><span>QGraphicsRectItem</span><span>(</span><span>QRectF</span><span>(</span><span>0</span><span>, </span><span>0</span><span>, </span><span>100</span><span>, </span><span>50</span><span>))</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>rect_item.</span><span>setBrush</span><span>(</span><span>QColor</span><span>(</span><span>0</span><span>, </span><span>255</span><span>, </span><span>0</span><span>, </span><span>150</span><span>))</span></div></div><div><div><div>49</div></div><div><span><span>        </span></span><span>rect_item.</span><span>setPen</span><span>(</span><span>QPen</span><span>(Qt.NoPen))</span></div></div><div><div><div>50</div></div><div><span>        </span><span>self</span><span><span>.scene.</span><span>addItem</span><span>(rect_item)</span></span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>ellipse_item </span><span>=</span><span> </span><span>QGraphicsEllipseItem</span><span>(</span><span>QRectF</span><span>(</span><span>50</span><span>, </span><span>50</span><span>, </span><span>80</span><span>, </span><span>80</span><span>))</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>ellipse_item.</span><span>setBrush</span><span>(</span><span>QColor</span><span>(</span><span>255</span><span>, </span><span>165</span><span>, </span><span>0</span><span>, </span><span>150</span><span>))</span></div></div><div><div><div>54</div></div><div><span><span>        </span></span><span>ellipse_item.</span><span>setPen</span><span>(</span><span>QPen</span><span>(Qt.NoPen))</span></div></div><div><div><div>55</div></div><div><span>        </span><span>self</span><span><span>.scene.</span><span>addItem</span><span>(ellipse_item)</span></span></div></div><div><div><div>56</div></div><div>
</div></div><div><div><div>57</div></div><div><span>        </span><span>self</span><span><span>.scene.</span><span>setSceneRect</span><span>(</span></span><span>self</span><span><span>.scene.</span><span>itemsBoundingRect</span><span>())</span></span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></span></div></div><div><div><div>60</div></div><div><span><span>window </span><span>=</span><span> </span><span>GraphicsViewDemo</span><span>()</span></span></div></div><div><div><div>61</div></div><div><span><span>window.</span><span>show</span><span>()</span></span></div></div><div><div><div>62</div></div><div><span><span>app.</span><span>exec</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>核心概念：</strong></p><ul>
<li><strong>QPainter</strong>: 用于低级绘图，直接在 <code>QWidget</code> 上绘制</li>
<li><strong>paintEvent</strong>: <code>QWidget</code> 的事件处理方法，用于自定义绘制</li>
<li><strong>QGraphicsScene</strong>: 管理 2D 图形项的容器</li>
<li><strong>QGraphicsView</strong>: 显示 <code>QGraphicsScene</code> 内容的视图</li>
<li><strong>QGraphicsItem</strong>: 场景中的基本图形项（如 <code>QGraphicsRectItem</code>, <code>QGraphicsEllipseItem</code>）</li>
</ul></section><section><h2>部署<a href="#部署"><span>#</span></a></h2><p>将 PySide6 应用程序打包成可执行文件，方便在没有 Python 环境的机器上运行。常用的工具是 <code>PyInstaller</code>。</p><section><h4>使用 PyInstaller 打包<a href="#使用-pyinstaller-打包"><span>#</span></a></h4><ol>
<li>
<p><strong>安装 PyInstaller</strong>:</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>pyinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p><strong>创建打包命令</strong>:
假设你的主 PySide6 应用程序文件是 <code>main.py</code>。</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pyinstaller</span><span> </span><span>--onefile</span><span> </span><span>--windowed</span><span> </span><span>main.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<ul>
<li><code>--onefile</code>: 将所有内容打包成一个单独的可执行文件。</li>
<li><code>--windowed</code> 或 <code>-w</code>: 禁用命令行窗口（对于 GUI 应用程序）。</li>
</ul>
</li>
<li>
<p><strong>添加图标 (可选)</strong>:</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pyinstaller</span><span> </span><span>--onefile</span><span> </span><span>--windowed</span><span> </span><span>--icon=app.ico</span><span> </span><span>main.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<ul>
<li><code>--icon=app.ico</code>: 指定应用程序图标文件（<code>.ico</code> 格式）。</li>
</ul>
</li>
<li>
<p><strong>处理资源文件 (可选)</strong>:
如果你的应用程序使用了图片、配置文件等资源文件，需要通过 <code>--add-data</code> 参数将其包含进去。
例如，如果 <code>images</code> 文件夹在 <code>main.py</code> 同级目录下：</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pyinstaller</span><span> </span><span>--onefile</span><span> </span><span>--windowed</span><span> </span><span>--add-data</span><span> </span><span>"images;images"</span><span> </span><span>main.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<ul>
<li>格式为 <code>源路径;目标路径</code>。在 Windows 上使用 <code>;</code> 分隔，在 Linux/macOS 上使用 <code>:</code> 分隔。</li>
</ul>
</li>
<li>
<p><strong>运行打包后的程序</strong>:
打包成功后，可执行文件会在 <code>dist</code> 文件夹中。</p>
</li>
</ol><p><strong>核心概念：</strong></p><ul>
<li><strong>PyInstaller</strong>: 将 Python 应用程序及其所有依赖项打包成独立可执行文件的工具。</li>
<li><strong><code>--onefile</code></strong>: 生成单个可执行文件，便于分发。</li>
<li><strong><code>--windowed</code></strong>: 隐藏控制台窗口，适用于 GUI 应用。</li>
<li><strong><code>--add-data</code></strong>: 包含非代码资源文件。</li>
</ul></section><section><h3>使用Qt Designer创建完整应用示例<a href="#使用qt-designer创建完整应用示例"><span>#</span></a></h3><p>下面是一个使用Qt Designer创建简单文本编辑器的完整示例：</p><ol>
<li>在Qt Designer中创建一个MainWindow，添加菜单栏、工具栏和中央文本编辑器</li>
<li>保存为texteditor.ui</li>
<li>使用pyside6-uic转换为Python代码</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pyside6-uic</span><span> </span><span>texteditor.ui</span><span> </span><span>-o</span><span> </span><span>ui_texteditor.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>创建主应用程序文件：</li>
</ol><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>from</span><span> PySide6.QtWidgets </span><span>import</span><span> QApplication, QMainWindow, QFileDialog, QMessageBox</span></div></div><div><div><div>3</div></div><div><span>from</span><span> PySide6.QtCore </span><span>import</span><span> QFile, QTextStream</span></div></div><div><div><div>4</div></div><div><span>from</span><span> ui_texteditor </span><span>import</span><span> Ui_MainWindow</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>class</span><span><span> </span><span>TextEditor</span><span>(</span><span>QMainWindow</span><span>)</span></span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>8</div></div><div><span>        </span><span>super</span><span>().</span><span>__init__</span><span>()</span></div></div><div><div><div>9</div></div><div><span>        </span><span>self</span><span><span>.ui </span><span>=</span><span> </span><span>Ui_MainWindow</span><span>()</span></span></div></div><div><div><div>10</div></div><div><span>        </span><span>self</span><span><span>.ui.</span><span>setupUi</span><span>(</span></span><span>self</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>        </span><span># 当前文件路径</span></div></div><div><div><div>13</div></div><div><span>        </span><span>self</span><span><span>.current_file </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>        </span><span># 连接信号和槽</span></div></div><div><div><div>16</div></div><div><span>        </span><span>self</span><span><span>.ui.actionNew.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.new_file)</span></div></div><div><div><div>17</div></div><div><span>        </span><span>self</span><span><span>.ui.actionOpen.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.open_file)</span></div></div><div><div><div>18</div></div><div><span>        </span><span>self</span><span><span>.ui.actionSave.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.save_file)</span></div></div><div><div><div>19</div></div><div><span>        </span><span>self</span><span><span>.ui.actionSaveAs.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.save_file_as)</span></div></div><div><div><div>20</div></div><div><span>        </span><span>self</span><span><span>.ui.actionExit.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.close)</span></div></div><div><div><div>21</div></div><div><span>        </span><span>self</span><span><span>.ui.actionAbout.triggered.</span><span>connect</span><span>(</span></span><span>self</span><span>.about)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>        </span><span># 设置窗口标题</span></div></div><div><div><div>24</div></div><div><span>        </span><span>self</span><span><span>.</span><span>update_title</span><span>()</span></span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>    </span><span>def</span><span> </span><span>new_file</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>27</div></div><div><span>        </span><span>self</span><span><span>.ui.textEdit.</span><span>clear</span><span>()</span></span></div></div><div><div><div>28</div></div><div><span>        </span><span>self</span><span><span>.current_file </span><span>=</span><span> </span></span><span>None</span></div></div><div><div><div>29</div></div><div><span>        </span><span>self</span><span><span>.</span><span>update_title</span><span>()</span></span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>    </span><span>def</span><span> </span><span>open_file</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>32</div></div><div><span><span>        </span></span><span>file_name, _ </span><span>=</span><span> QFileDialog.</span><span>getOpenFileName</span><span>(</span><span>self</span><span>, </span><span>"打开文件"</span><span>, </span><span>""</span><span>, </span><span>"文本文件 (*.txt);;所有文件 (*)"</span><span>)</span></div></div><div><div><div>33</div></div><div><span>        </span><span>if</span><span> file_name:</span></div></div><div><div><div>34</div></div><div><span>            </span><span>try</span><span>:</span></div></div><div><div><div>35</div></div><div><span>                </span><span>file</span><span><span> </span><span>=</span><span> </span><span>QFile</span><span>(file_name)</span></span></div></div><div><div><div>36</div></div><div><span>                </span><span>if</span><span> </span><span>file</span><span><span>.</span><span>open</span><span>(QFile.ReadOnly </span><span>|</span><span> QFile.Text):</span></span></div></div><div><div><div>37</div></div><div><span><span>                    </span></span><span>stream </span><span>=</span><span> </span><span>QTextStream</span><span>(</span><span>file</span><span>)</span></div></div><div><div><div>38</div></div><div><span>                    </span><span>self</span><span><span>.ui.textEdit.</span><span>setText</span><span>(stream.</span><span>readAll</span><span>())</span></span></div></div><div><div><div>39</div></div><div><span>                    </span><span>file</span><span><span>.</span><span>close</span><span>()</span></span></div></div><div><div><div>40</div></div><div><span>                    </span><span>self</span><span><span>.current_file </span><span>=</span><span> file_name</span></span></div></div><div><div><div>41</div></div><div><span>                    </span><span>self</span><span><span>.</span><span>update_title</span><span>()</span></span></div></div><div><div><div>42</div></div><div><span>            </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>43</div></div><div><span><span>                </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>f</span><span>"无法打开文件: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>    </span><span>def</span><span> </span><span>save_file</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>46</div></div><div><span>        </span><span>if</span><span> </span><span>self</span><span>.current_file:</span></div></div><div><div><div>47</div></div><div><span>            </span><span>self</span><span><span>.</span><span>save_to_file</span><span>(</span></span><span>self</span><span>.current_file)</span></div></div><div><div><div>48</div></div><div><span>        </span><span>else</span><span>:</span></div></div><div><div><div>49</div></div><div><span>            </span><span>self</span><span><span>.</span><span>save_file_as</span><span>()</span></span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span>    </span><span>def</span><span> </span><span>save_file_as</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>file_name, _ </span><span>=</span><span> QFileDialog.</span><span>getSaveFileName</span><span>(</span><span>self</span><span>, </span><span>"保存文件"</span><span>, </span><span>""</span><span>, </span><span>"文本文件 (*.txt);;所有文件 (*)"</span><span>)</span></div></div><div><div><div>53</div></div><div><span>        </span><span>if</span><span> file_name:</span></div></div><div><div><div>54</div></div><div><span>            </span><span>self</span><span><span>.</span><span>save_to_file</span><span>(file_name)</span></span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>    </span><span>def</span><span> </span><span>save_to_file</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>file_name</span></span><span>):</span></div></div><div><div><div>57</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>58</div></div><div><span>            </span><span>file</span><span><span> </span><span>=</span><span> </span><span>QFile</span><span>(file_name)</span></span></div></div><div><div><div>59</div></div><div><span>            </span><span>if</span><span> </span><span>file</span><span><span>.</span><span>open</span><span>(QFile.WriteOnly </span><span>|</span><span> QFile.Text):</span></span></div></div><div><div><div>60</div></div><div><span><span>                </span></span><span>stream </span><span>=</span><span> </span><span>QTextStream</span><span>(</span><span>file</span><span>)</span></div></div><div><div><div>61</div></div><div><span><span>                </span></span><span>stream </span><span>&lt;&lt;</span><span> </span><span>self</span><span><span>.ui.textEdit.</span><span>toPlainText</span><span>()</span></span></div></div><div><div><div>62</div></div><div><span>                </span><span>file</span><span><span>.</span><span>close</span><span>()</span></span></div></div><div><div><div>63</div></div><div><span>                </span><span>self</span><span><span>.current_file </span><span>=</span><span> file_name</span></span></div></div><div><div><div>64</div></div><div><span>                </span><span>self</span><span><span>.</span><span>update_title</span><span>()</span></span></div></div><div><div><div>65</div></div><div><span>                </span><span>return</span><span> </span><span>True</span></div></div><div><div><div>66</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span> </span><span>as</span><span> e:</span></div></div><div><div><div>67</div></div><div><span><span>            </span></span><span>QMessageBox.</span><span>critical</span><span>(</span><span>self</span><span>, </span><span>"错误"</span><span>, </span><span>f</span><span>"无法保存文件: </span><span>{</span><span>e</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>68</div></div><div><span>        </span><span>return</span><span> </span><span>False</span></div></div><div><div><div>69</div></div><div>
</div></div><div><div><div>70</div></div><div><span>    </span><span>def</span><span> </span><span>update_title</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>71</div></div><div><span><span>        </span></span><span>title </span><span>=</span><span> </span><span>"无标题"</span><span> </span><span>if</span><span> </span><span>not</span><span> </span><span>self</span><span>.current_file </span><span>else</span><span> </span><span>self</span><span>.current_file</span></div></div><div><div><div>72</div></div><div><span>        </span><span>self</span><span><span>.</span><span>setWindowTitle</span><span>(</span></span><span>f</span><span>"</span><span>{</span><span>title</span><span>}</span><span> - 文本编辑器"</span><span>)</span></div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span>    </span><span>def</span><span> </span><span>about</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>75</div></div><div><span><span>        </span></span><span>QMessageBox.</span><span>about</span><span>(</span><span>self</span><span>, </span><span>"关于"</span><span>, </span><span>"使用PySide6和Qt Designer创建的简单文本编辑器"</span><span>)</span></div></div><div><div><div>76</div></div><div>
</div></div><div><div><div>77</div></div><div><span>    </span><span>def</span><span> </span><span>closeEvent</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>event</span></span><span>):</span></div></div><div><div><div>78</div></div><div><span>        </span><span>if</span><span> </span><span>self</span><span><span>.ui.textEdit.</span><span>document</span><span>().</span><span>isModified</span><span>():</span></span></div></div><div><div><div>79</div></div><div><span><span>            </span></span><span>reply </span><span>=</span><span> QMessageBox.</span><span>question</span><span>(</span><span>self</span><span>, </span><span>"保存更改"</span><span>,</span></div></div><div><div><div>80</div></div><div><span>                                       </span><span>"文档已被修改，是否保存更改？"</span><span>,</span></div></div><div><div><div>81</div></div><div><span><span>                                       </span></span><span>QMessageBox.Save </span><span>|</span><span> QMessageBox.Discard </span><span>|</span><span> QMessageBox.Cancel)</span></div></div><div><div><div>82</div></div><div>
</div></div><div><div><div>83</div></div><div><span>            </span><span>if</span><span><span> reply </span><span>==</span><span> QMessageBox.Save:</span></span></div></div><div><div><div>84</div></div><div><span>                </span><span>if</span><span> </span><span>not</span><span> </span><span>self</span><span><span>.</span><span>save_file</span><span>():</span></span></div></div><div><div><div>85</div></div><div><span><span>                    </span></span><span>event.</span><span>ignore</span><span>()</span></div></div><div><div><div>86</div></div><div><span>            </span><span>elif</span><span><span> reply </span><span>==</span><span> QMessageBox.Cancel:</span></span></div></div><div><div><div>87</div></div><div><span><span>                </span></span><span>event.</span><span>ignore</span><span>()</span></div></div><div><div><div>88</div></div><div>
</div></div><div><div><div>89</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>90</div></div><div><span><span>    </span></span><span>app </span><span>=</span><span> </span><span>QApplication</span><span>(sys.argv)</span></div></div><div><div><div>91</div></div><div><span><span>    </span></span><span>window </span><span>=</span><span> </span><span>TextEditor</span><span>()</span></div></div><div><div><div>92</div></div><div><span><span>    </span></span><span>window.</span><span>show</span><span>()</span></div></div><div><div><div>93</div></div><div><span><span>    </span></span><span>sys.</span><span>exit</span><span>(app.</span><span>exec</span><span>())</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这个示例展示了如何将Qt Designer创建的UI与PySide6代码结合，实现一个功能完整的文本编辑器应用程序。</p></section></section></section>]]></content>
    </entry>
</feed>
