<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>维燕的知识花园</title><description>A Bioinformatics Engineer.</description><link>https://shenweiyan.github.io/</link><language>zh</language><item><title>使用 Python 的 argparse 构建命令行界面</title><link>https://shenweiyan.github.io/posts/6/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/6/</guid><description>原文：Build Command-Line Interfaces With Python&apos;s argparse 命令行应用在普通用户空间中可能并不常见，但它们存在于开发、数据科学、系统管理和许多其他操作中。每个命令行应用都需要一个用户友好的命令行界面 （CLI），以便你可以与应用本身进行交互。在 Python 中，您可以使用标准库中的 argparse 模块创建功能齐全的 CLI。 在本文中，你将…</description><pubDate>Thu, 29 Oct 2026 02:52:26 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文：&lt;a href=&quot;https://realpython.com/command-line-interfaces-python-argparse/&quot;&gt;Build Command-Line Interfaces With Python&apos;s argparse&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;命令行应用在普通用户空间中可能并不常见，但它们存在于开发、数据科学、系统管理和许多其他操作中。每个命令行应用都需要一个用户友好的命令行界面 （CLI），以便你可以与应用本身进行交互。在 Python 中，您可以使用标准库中的 &lt;code&gt;argparse&lt;/code&gt; 模块创建功能齐全的 CLI。&lt;/p&gt;

&lt;p&gt;在本文中，你将了解如何：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令行界面入门；&lt;/li&gt;
&lt;li&gt;在 Python 中组织和布局命令行应用项目；&lt;/li&gt;
&lt;li&gt;使用 Python &lt;code&gt;argparse&lt;/code&gt; 创建命令行界面（command-line interfaces）；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;argparse&lt;/code&gt; 一些强大的功能深度自定义您的 CLI；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若要充分利用本教程，应熟悉 Python 编程，包括面向对象编程、脚本开发和执行以及 Python 包和模块等概念。如果您熟悉与使用命令行或终端相关的一般概念和主题，这也将很有帮助。&lt;/p&gt;
&lt;h2 id=&quot;user-content-了解命令行界面&quot;&gt;了解命令行界面&lt;/h2&gt;
&lt;p&gt;自从计算机发明以来，人类一直需要并找到与这些机器交互和共享信息的方法。信息交换在人、计算机软件和硬件组件之间流动。其中任意两个元素之间的共享边界通常称为接口(&lt;a href=&quot;https://en.wikipedia.org/wiki/Interface_(computing)&quot;&gt;interface&lt;/a&gt;)。&lt;/p&gt;
&lt;p&gt;在软件开发中，接口是给定软件的特殊部分，它允许计算机系统组件之间的交互。当涉及到人机交互和软件交互时，这个重要的组件被称为用户界面（&lt;a href=&quot;https://en.wikipedia.org/wiki/User_interface&quot;&gt;user interface&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;您会在编程中找到不同类型的用户界面。图形用户界面 （GUI） 可能是当今最常见的。但是，您还可以找到为其用户提供命令行界面 （CLI） 的应用和程序。在本教程中，你将了解 CLI 以及如何在 Python 中创建它们。&lt;/p&gt;
&lt;h2 id=&quot;user-content-命令行界面-cli&quot;&gt;命令行界面 （CLI）&lt;/h2&gt;
&lt;p&gt;命令行界面允许您通过操作系统命令行、终端或控制台与应用程序或程序进行交互。&lt;/p&gt;
&lt;p&gt;要了解命令行界面及其工作原理，请考虑此实际示例。假设您有一个名为 sample 包含三个示例文件的目录。如果您使用的是类 Unix 操作系统，例如 Linux 或 macOS，请继续在父目录中打开命令行窗口或终端，然后执行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ls sample/
hello.txt     lorem.md      realpython.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unix 的 &lt;code&gt;ls&lt;/code&gt; 命令列出目标目录中包含的文件和子目录，该目录默认为当前工作目录。上面的命令调用没有显示有关 的内容 sample 的太多信息。它只在屏幕上显示文件名。&lt;/p&gt;
&lt;p&gt;假设你想要获取有关目录及其内容的更丰富信息，那么你不需要寻找其他程序，因为 ls 命令有一个功能齐全的命令行界面，并且提供了一组有用的选项，可以用来定制命令的行为。&lt;/p&gt;
&lt;p&gt;例如，继续执行带有 &lt;code&gt;-l&lt;/code&gt; 选项的 &lt;code&gt;ls&lt;/code&gt; 命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ls -l sample/
total 24
-rw-r--r--@ 1 user  staff    83 Aug 17 22:15 hello.txt
-rw-r--r--@ 1 user  staff  2609 Aug 17 22:15 lorem.md
-rw-r--r--@ 1 user  staff   428 Aug 17 22:15 realpython.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，&lt;code&gt;ls&lt;/code&gt; 命令的输出完全不同了。该命令显示了有关 sample 目录中文件的更多信息，包括权限、所有者、组、日期和大小。它还显示了这些文件在你计算机磁盘上使用的总空间。&lt;/p&gt;
&lt;p&gt;这种更丰富的输出结果是由于使用了 &lt;code&gt;-l&lt;/code&gt; 选项，这是 Unix &lt;code&gt;ls&lt;/code&gt; 命令行界面的一部分，它启用了详细的输出格式。&lt;/p&gt;
&lt;h2 id=&quot;user-content-命令参数选项参数和子命令&quot;&gt;命令、参数、选项、参数和子命令&lt;/h2&gt;
&lt;p&gt;在本教程中，您将深入了解&lt;strong&gt;命令&lt;/strong&gt;(commands)及其&lt;strong&gt;子命令&lt;/strong&gt;(subcommands)，同时还会学习到&lt;strong&gt;命令行参数&lt;/strong&gt;(command-line arguments)、&lt;strong&gt;选项&lt;/strong&gt;(options)和&lt;strong&gt;参数&lt;/strong&gt;(parameters)的相关知识。因此，建议您将这些术语纳入您的技术词汇库中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;命令(Command)&lt;/strong&gt;：在命令行或终端窗口中运行的程序或例程。通常，您可以通过其背后的程序(underlying program)或例程(routine)的名称来识别一个命令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数(Argument)&lt;/strong&gt;：命令在执行其预期操作时所需或可选的信息片段。命令通常接受一个或多个参数，您可以在命令行中以空格分隔或逗号分隔的列表形式提供这些参数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选项(Option)&lt;/strong&gt;，也称为 &lt;strong&gt;flag&lt;/strong&gt; 或 &lt;strong&gt;switch&lt;/strong&gt;：一种可选的参数，用于修改命令的行为。选项通过特定的名称（如前一个示例中的 &lt;code&gt;-l&lt;/code&gt;）传递给命令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数(Parameter)&lt;/strong&gt;：一个选项用于执行其预期操作或动作时所使用的参数。&lt;/li&gt;
&lt;li&gt;子命令(Subcommand)**：一个预定义的名称，可以传递给应用程序来执行特定的操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考上一节中的示例命令结构：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ls -l sample/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，您组合了命令行界面（CLI）的以下组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ls&lt;/strong&gt;：命令的名称或应用的名称；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-l&lt;/strong&gt;：启用详细输出的选项(option)、开关(switch)或标志(flag)；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sample&lt;/strong&gt;：为命令执行提供附加信息的参数(argument)；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在，让我们来看下面的命令结构，它展示了 Python 包管理器 &lt;code&gt;pip&lt;/code&gt; 的命令行界面（CLI）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个常见的 &lt;code&gt;pip&lt;/code&gt; 命令结构，您可能之前已经见过。它允许您使用 &lt;code&gt;requirements.txt&lt;/code&gt; 文件来给指定的 Python 项目安装依赖项。在这个例子中，您使用了以下命令行界面（CLI）组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;pip&lt;/strong&gt;：命令的名称；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;install&lt;/strong&gt;：&lt;code&gt;pip&lt;/code&gt; 的子命令(subcommand)名称；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-r&lt;/strong&gt;：&lt;code&gt;install&lt;/code&gt; 子命令的选项(option)；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;requirements.txt&lt;/strong&gt;：一个参数，特别是 &lt;code&gt;-r&lt;/code&gt; 选项的参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在您已经了解了命令行界面（CLI）是什么以及其主要部分或组件有哪些。接下来，是时候学习如何在 Python 中创建自己的 CLI 了。&lt;/p&gt;
&lt;h2 id=&quot;user-content-python-中的-cli-入门&quot;&gt;Python 中的 CLI 入门&lt;/h2&gt;
&lt;p&gt;Python 附带了一些工具，这些工具可帮助您为程序和应用程序编写命令行界面（CLI）。若您需要快速为小型程序构建一个简洁的 CLI，那么可以利用 &lt;a href=&quot;https://docs.python.org/3/library/sys.html#module-sys&quot;&gt;&lt;code&gt;sys&lt;/code&gt;&lt;/a&gt; 模块中的 &lt;a href=&quot;https://docs.python.org/3/library/sys.html#sys.argv&quot;&gt;&lt;code&gt;argv&lt;/code&gt;&lt;/a&gt; 属性。这个属性会自动存储您在命令行中传递给特定程序的参数。&lt;/p&gt;
&lt;h3 id=&quot;user-content-使用-sysargv-构建最小的-cli&quot;&gt;使用 &lt;code&gt;sys.argv&lt;/code&gt; 构建最小的 CLI&lt;/h3&gt;
&lt;p&gt;以使用 &lt;code&gt;argv&lt;/code&gt; 创建最小命令行界面（CLI）为例，假设您需要编写一个小程序，该程序类似于 &lt;code&gt;ls&lt;/code&gt; 命令，能够列出给定目录下的所有文件。在这种情况下，您可以编写如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls_argv.py

import sys
from pathlib import Path

if (args_count := len(sys.argv)) &gt; 2:
    print(f&quot;One argument expected, got {args_count - 1}&quot;)
    raise SystemExit(2)
elif args_count &amp;#x3C; 2:
    print(&quot;You must specify the target directory&quot;)
    raise SystemExit(2)

target_dir = Path(sys.argv[1])

if not target_dir.is_dir():
    print(&quot;The target directory doesn&apos;t exist&quot;)
    raise SystemExit(1)

for entry in target_dir.iterdir():
    print(entry.name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该程序通过手动处理命令行提供的参数来实现了一个简单的命令行界面（CLI），这些参数会自动存储在 &lt;code&gt;sys.argv&lt;/code&gt; 中。&lt;code&gt;sys.argv&lt;/code&gt; 的第一个元素始终是程序名称，第二个元素则是目标目录。由于应用程序不应接受超过一个目标目录，因此 &lt;code&gt;args_count&lt;/code&gt; 不得超过 2。&lt;/p&gt;
&lt;p&gt;在检查 &lt;code&gt;sys.argv&lt;/code&gt; 的内容后，您创建一个&lt;code&gt;pathlib.Path&lt;/code&gt;对象来存储目标目录的路径。如果该目录不存在，您将通知用户并退出程序。接下来的&lt;code&gt;for&lt;/code&gt;循环将列出目录内容，每行一个条目。&lt;/p&gt;
&lt;p&gt;如果从命令行运行该脚本，您将得到以下结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls_argv.py sample/
hello.txt
lorem.md
realpython.md

$ python ls_argv.py
You must specify the target directory

$ python ls_argv.py sample/ other_dir/
One argument expected, got 2

$ python ls_argv.py non_existing/
The target directory doesn&apos;t exist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您的程序接受一个目录作为参数，并列出其内容。如果您运行命令时没有提供参数，您将收到一个错误消息。如果您运行命令时指定了超过一个目标目录，您同样会收到一个错误消息。如果尝试运行命令并指定一个不存在的目录，程序将输出另一个错误消息。&lt;/p&gt;
&lt;p&gt;虽然您的程序运行正常，但对于更复杂的 CLI 应用程序来说，使用&lt;code&gt;sys.argv&lt;/code&gt;属性手动解析命令行参数并不是一个可扩展的解决方案。如果您的应用需要接受更多的参数和选项，那么解析&lt;code&gt;sys.argv&lt;/code&gt;将变得复杂且容易出错。您需要一个更好的解决方案，这就是 Python 中的&lt;code&gt;argparse&lt;/code&gt;模块所提供的。&lt;/p&gt;
&lt;h3 id=&quot;user-content-使用-argparse-创建-cli&quot;&gt;使用 &lt;code&gt;argparse&lt;/code&gt; 创建 CLI&lt;/h3&gt;
&lt;p&gt;在 Python 中创建 CLI 应用程序的更便捷方法是使用标准库中的 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html?highlight=argparse#module-argparse&quot;&gt;&lt;code&gt;argparse&lt;/code&gt;&lt;/a&gt; 模块。该模块首次在 Python 3.2 中随 &lt;a href=&quot;https://www.python.org/dev/peps/pep-0389/&quot;&gt;PEP-389&lt;/a&gt; 一同发布，是快速创建 Python CLI 应用程序的利器，无需安装如 Typer 或 Click 这样的第三方库。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;argparse&lt;/code&gt; 模块是作为较旧的 &lt;a href=&quot;https://docs.python.org/3/library/getopt.html&quot;&gt;&lt;code&gt;getopt&lt;/code&gt;&lt;/a&gt; 和 &lt;a href=&quot;https://docs.python.org/3/library/optparse.html&quot;&gt;&lt;code&gt;optparse&lt;/code&gt;&lt;/a&gt; 模块的替代品而发布的，因为它们缺乏一些重要的功能。&lt;/p&gt;
&lt;p&gt;Python 的 &lt;code&gt;argparse&lt;/code&gt; 模块允许您：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解析命令行&lt;strong&gt;参数&lt;/strong&gt;(arguments)和&lt;strong&gt;选项&lt;/strong&gt;(options)；&lt;/li&gt;
&lt;li&gt;在一个单一选项中接受&lt;strong&gt;可变数量的参数&lt;/strong&gt;(variable number of parameters)；&lt;/li&gt;
&lt;li&gt;在 CLI 中提供子命令(subcommands)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些特性使 &lt;code&gt;argparse&lt;/code&gt; 成为了一个强大的 CLI 框架，您在创建 CLI 应用程序时可以放心地依赖它。要使用 Python 的 &lt;code&gt;argparse&lt;/code&gt;，您需要遵循以下四个简单的步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导入 &lt;code&gt;argparse&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;通过实例化 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser&quot;&gt;&lt;code&gt;ArgumentParser&lt;/code&gt;&lt;/a&gt; 创建&lt;strong&gt;参数解析器&lt;/strong&gt;(argument parser)；&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument&quot;&gt;&lt;code&gt;.add_argument()&lt;/code&gt;&lt;/a&gt; 方法向解析器添加&lt;strong&gt;参数&lt;/strong&gt;(arguments)和&lt;strong&gt;选项&lt;/strong&gt;(options)；&lt;/li&gt;
&lt;li&gt;在解析器上调用 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html?highlight=argparse#argparse.ArgumentParser.parse_args&quot;&gt;&lt;code&gt;.parse_args()&lt;/code&gt;&lt;/a&gt; 以获取参数 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#namespace&quot;&gt;&lt;code&gt;Namespace&lt;/code&gt;&lt;/a&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如，您可以使用 &lt;code&gt;argparse&lt;/code&gt; 来改进您的 &lt;code&gt;ls_argv.py&lt;/code&gt; 脚本。现在，您可以创建一个名为 &lt;code&gt;ls.py&lt;/code&gt; 的脚本，并编写以下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v1

import argparse
from pathlib import Path

parser = argparse.ArgumentParser()

parser.add_argument(&quot;path&quot;)

args = parser.parse_args()

target_dir = Path(args.path)

if not target_dir.exists():
    print(&quot;The target directory doesn&apos;t exist&quot;)
    raise SystemExit(1)

for entry in target_dir.iterdir():
    print(entry.name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随着 &lt;code&gt;argparse&lt;/code&gt; 的引入，您的代码发生了显著的变化。与之前的版本相比，最明显的不同是，用于检查用户提供的参数的条件语句已经消失了。这是因为 &lt;code&gt;argparse&lt;/code&gt; 会自动为您检查参数的存在性。&lt;/p&gt;
&lt;p&gt;在这个新的实现中，您首先导入 &lt;code&gt;argparse&lt;/code&gt; 并创建一个参数解析器。要创建解析器，您可以使用 &lt;code&gt;ArgumentParser&lt;/code&gt; 类。接下来，您定义一个名为 &lt;code&gt;path&lt;/code&gt; 的参数，用于获取用户的目标目录。&lt;/p&gt;
&lt;p&gt;接下来，您需要调用 &lt;code&gt;.parse_args()&lt;/code&gt; 方法来解析输入参数，并获取一个包含所有用户参数的 &lt;code&gt;Namespace&lt;/code&gt; 对象。请注意，现在 &lt;code&gt;args&lt;/code&gt; 变量保存了一个 &lt;code&gt;Namespace&lt;/code&gt; 对象，该对象具有从命令行收集的每个参数所对应的属性。&lt;/p&gt;
&lt;p&gt;在这个例子中，您只有一个参数，名为 &lt;code&gt;path&lt;/code&gt;。&lt;code&gt;Namespace&lt;/code&gt; 对象允许您使用点表示法通过 &lt;code&gt;args&lt;/code&gt; 来访问 &lt;code&gt;path&lt;/code&gt;。其余的代码与第一个实现相同。&lt;/p&gt;
&lt;p&gt;现在继续从命令行运行这个新脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py sample/
lorem.md
realpython.md
hello.txt

$ python ls.py
usage: ls.py [-h] path
ls.py: error: the following arguments are required: path

$ python ls.py sample/ other_dir/
usage: ls.py [-h] path
ls.py: error: unrecognized arguments: other_dir/

$ python ls.py non_existing/
The target directory doesn&apos;t exist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一个命令的输出与您的原始脚本 &lt;code&gt;ls_argv.py&lt;/code&gt; 相同。而第二个命令的输出则与 &lt;code&gt;ls_argv.py&lt;/code&gt; 中的输出大不相同。程序现在会显示一个使用说明消息，并发出错误提示，告诉您必须提供 &lt;code&gt;path&lt;/code&gt; 参数。&lt;/p&gt;
&lt;p&gt;在第三个命令中，您传递了两个目标目录，但应用程序并未为此做好准备。因此，它再次显示使用说明消息，并抛出一个错误，告知您潜在的问题。&lt;/p&gt;
&lt;p&gt;最后，如果您运行脚本时传递了一个不存在的目录作为参数，那么您会收到一个错误，告知您目标目录不存在，因此程序无法执行其工作。&lt;/p&gt;
&lt;p&gt;现在，您可以使用一个新的隐式特性。现在，您的程序接受一个可选的 &lt;code&gt;-h&lt;/code&gt; 标志。不妨尝试一下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py -h
usage: ls.py [-h] path

positional arguments:
  path

options:
  -h, --help  show this help message and exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;太棒了，现在您的程序会自动响应 &lt;code&gt;-h&lt;/code&gt; 或 &lt;code&gt;--help&lt;/code&gt; 标志，并为您显示带有使用说明的帮助消息。这真是一个很棒的特性，而且您只需在代码中引入 &lt;code&gt;argparse&lt;/code&gt; 就能轻松获得！&lt;/p&gt;
&lt;p&gt;经过这个快速介绍如何在 Python 中创建 CLI 应用后，您现在就可以深入研究 &lt;code&gt;argparse&lt;/code&gt; 模块及其所有炫酷特性了。&lt;/p&gt;
&lt;h2 id=&quot;user-content-使用-python-的-argparse-创建-cli&quot;&gt;使用 Python 的 argparse 创建 CLI&lt;/h2&gt;
&lt;p&gt;您可以使用 &lt;code&gt;argparse&lt;/code&gt; 模块为您的应用程序和项目编写用户友好的命令行界面。该模块允许您定义应用程序所需的参数和选项。然后，&lt;code&gt;argparse&lt;/code&gt; 将负责为您解析 &lt;code&gt;sys.argv&lt;/code&gt; 的参数和选项。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;argparse&lt;/code&gt; 的另一个酷炫特性是它可以自动为您的 CLI 应用程序生成使用说明和帮助消息。该模块还会在参数无效时发出错误提示，并具备更多功能。&lt;/p&gt;
&lt;p&gt;在深入研究 &lt;code&gt;argparse&lt;/code&gt; 之前，您需要知道该模块的文档可识别两种不同类型的命令行参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;位置参数&lt;/strong&gt;(Positional arguments)，您称为参数(arguments)；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可选参数&lt;/strong&gt;(Optional arguments)，即选项(options)、标志(flags)或开关(switches)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;ls.py&lt;/code&gt; 的示例中，&lt;code&gt;path&lt;/code&gt; 是一个&lt;strong&gt;位置参数&lt;/strong&gt;(positional argument)。这样的参数之所以被称为位置参数，是因为它在命令构造中的相对位置定义了其作用。&lt;/p&gt;
&lt;p&gt;与位置参数不同，&lt;strong&gt;可选参数&lt;/strong&gt;(Optional arguments)并不是必需的。它们允许你修改命令的行为。以 Unix 命令 &lt;code&gt;ls&lt;/code&gt; 为例，&lt;code&gt;-l&lt;/code&gt; 标志就是一个可选参数，它使得命令以详细模式显示输出。&lt;/p&gt;
&lt;p&gt;在明确了这些概念之后，你就可以着手使用 Python 和 &lt;code&gt;argparse&lt;/code&gt; 库来构建自己的命令行界面（CLI）应用程序了。&lt;/p&gt;
&lt;h3 id=&quot;user-content-创建命令行参数解析器&quot;&gt;创建命令行参数解析器&lt;/h3&gt;
&lt;p&gt;命令行参数解析器是任何使用 &lt;code&gt;argparse&lt;/code&gt; 的命令行界面（CLI）中最为关键的部分。你在命令行上提供的所有参数和选项都会经过这个解析器的处理，它会为你完成繁重的解析工作。&lt;/p&gt;
&lt;p&gt;要使用 &lt;code&gt;argparse&lt;/code&gt; 创建命令行参数解析器，您需要实例化 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#argumentparser-objects&quot;&gt;&lt;code&gt;ArgumentParser&lt;/code&gt;&lt;/a&gt; 类：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&gt;&gt;&gt; from argparse import ArgumentParser

&gt;&gt;&gt; parser = ArgumentParser()
&gt;&gt;&gt; parser
ArgumentParser(
    prog=&apos;&apos;,
    usage=None,
    description=None,
    formatter_class=&amp;#x3C;class &apos;argparse.HelpFormatter&apos;&gt;,
    conflict_handler=&apos;error&apos;,
    add_help=True
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ArgumentParser&lt;/code&gt; 的构造函数接受多种不同的参数，你可以利用这些参数来微调你的 CLI 的多个特性。由于所有这些参数都是可选的，因此你可以通过不传入任何参数直接实例化 &lt;code&gt;ArgumentParser&lt;/code&gt; 来创建一个最基本的解析器。&lt;/p&gt;
&lt;p&gt;在本教程中，你将会更深入地了解 &lt;code&gt;ArgumentParser&lt;/code&gt; 构造函数的参数，特别是在定制你的参数解析器的部分。目前，你可以开始使用 &lt;code&gt;argparse&lt;/code&gt; 创建 CLI 的下一步了。这一步就是通过解析器对象来添加参数和选项。&lt;/p&gt;
&lt;h3 id=&quot;user-content-添加参数和选项&quot;&gt;添加参数和选项&lt;/h3&gt;
&lt;p&gt;要为 &lt;code&gt;argparse&lt;/code&gt; 的 CLI 添加参数和选项，你将使用 &lt;code&gt;ArgumentParser&lt;/code&gt; 实例的 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#the-add-argument-method&quot;&gt;&lt;code&gt;.add_argument()&lt;/code&gt;&lt;/a&gt; 方法。请注意，这个方法对参数和选项都是通用的。在 &lt;code&gt;argparse&lt;/code&gt; 的术语中，参数被称为&lt;strong&gt;位置参数&lt;/strong&gt;(positional arguments)，而选项被称为&lt;strong&gt;可选参数&lt;/strong&gt;(optional arguments)。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.add_argument()&lt;/code&gt; 方法的第一个参数决定了参数和选项之间的区别。这个参数被标识为&lt;a href=&quot;https://docs.python.org/3/library/argparse.html?highlight=argparse#name-or-flags&quot;&gt;名称（name）或标志（flag）&lt;/a&gt;。因此，如果你提供一个 name，那么你将定义一个参数(argument)。相反，如果你使用一个 flag，那么你将添加一个选项(option)。&lt;/p&gt;
&lt;p&gt;你已经在使用 &lt;code&gt;argparse&lt;/code&gt; 时处理过命令行参数了。现在，考虑以下你自定义的 &lt;code&gt;ls&lt;/code&gt; 命令的增强版本，它向 CLI 添加了一个 &lt;code&gt;-l&lt;/code&gt; 选项：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v2

import argparse
import datetime
from pathlib import Path

parser = argparse.ArgumentParser()

parser.add_argument(&quot;path&quot;)

parser.add_argument(&quot;-l&quot;, &quot;--long&quot;, action=&quot;store_true&quot;)

args = parser.parse_args()

target_dir = Path(args.path)

if not target_dir.exists():
    print(&quot;The target directory doesn&apos;t exist&quot;)
    raise SystemExit(1)

def build_output(entry, long=False):
    if long:
        size = entry.stat().st_size
        date = datetime.datetime.fromtimestamp(
            entry.stat().st_mtime).strftime(
            &quot;%b %d %H:%M:%S&quot;
        )
        return f&quot;{size:&gt;6d} {date} {entry.name}&quot;
    return entry.name

for entry in target_dir.iterdir():
    print(build_output(entry, long=args.long))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，第 11 行代码创建了一个带有 &lt;code&gt;-l&lt;/code&gt; 和 &lt;code&gt;--long&lt;/code&gt; 标志的选项。参数(arguments)和选项(options)在语法上的区别在于，选项名称以短横线 &lt;code&gt;-&lt;/code&gt; 开头表示简写标志，以双短横线 &lt;code&gt;--&lt;/code&gt; 开头表示完整标志。&lt;/p&gt;
&lt;p&gt;请注意，在这个特定例子中，与 &lt;code&gt;-l&lt;/code&gt; 或 &lt;code&gt;--long&lt;/code&gt; 选项一同设置了一个 &lt;code&gt;action&lt;/code&gt; 参数为 &lt;code&gt;&quot;store_true&quot;&lt;/code&gt;，这意味着这个选项将存储一个布尔值。如果你在命令行上提供了这个选项，那么它的值将为 &lt;code&gt;True&lt;/code&gt;。如果你省略了这个选项，那么它的值将为 &lt;code&gt;False&lt;/code&gt;。在 &quot;设置 Option 背后的 Action&quot; 部分内容中，你将了解更多关于 &lt;code&gt;.add_argument()&lt;/code&gt; 中的 &lt;code&gt;action&lt;/code&gt; 参数的信息。&lt;/p&gt;
&lt;p&gt;在第 21 行的 &lt;code&gt;build_output()&lt;/code&gt; 函数中，当 &lt;code&gt;long&lt;/code&gt; 为 &lt;code&gt;True&lt;/code&gt; 时，它会返回一个详细的输出，否则返回一个简短的输出。详细的输出将包含目标目录中所有条目的大小、修改日期和名称。它使用了诸如 &lt;code&gt;Path.stat()&lt;/code&gt; 这样的工具，以及带有自定义字符串格式的 &lt;code&gt;datetime.datetime&lt;/code&gt; 对象。&lt;/p&gt;
&lt;p&gt;继续在 &lt;code&gt;sample&lt;/code&gt; 上执行您的程序，以检查 &lt;code&gt;-l&lt;/code&gt; 选项如何工作：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py -l sample/
  2609 Oct 28 14:07:04 lorem.md
   428 Oct 28 14:07:04 realpython.md
    83 Oct 28 14:07:04 hello.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新增的 &lt;code&gt;-l&lt;/code&gt; 选项允许你生成并显示关于目标目录内容的更详细输出。&lt;/p&gt;
&lt;p&gt;既然你已经知道了如何向 CLI 添加命令行参数和选项，接下来就是深入解析这些参数和选项的时候了。这将是你在接下来部分要探索的内容。&lt;/p&gt;
&lt;h3 id=&quot;user-content-解析命令行参数和选项&quot;&gt;解析命令行参数和选项&lt;/h3&gt;
&lt;p&gt;解析命令行参数是基于 &lt;code&gt;argparse&lt;/code&gt; 的任何 CLI 应用中的另一个重要步骤。一旦你解析了参数，你就可以根据它们的值来执行相应的操作。在你自定义的 &lt;code&gt;ls&lt;/code&gt; 命令示例中，参数解析发生在包含 &lt;code&gt;args = parser.parse_args()&lt;/code&gt; 语句的行上。&lt;/p&gt;
&lt;p&gt;这个语句调用了 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#the-parse-args-method&quot;&gt;&lt;code&gt;.parse_args()&lt;/code&gt;&lt;/a&gt; 方法，并将其返回值赋给 &lt;code&gt;args&lt;/code&gt; 变量。&lt;code&gt;.parse_args()&lt;/code&gt; 的返回值是一个 &lt;a href=&quot;https://docs.python.org/3/library/argparse.html#the-namespace-object&quot;&gt;&lt;code&gt;Namespace&lt;/code&gt;&lt;/a&gt; 对象，其中包含了在命令行上提供的所有参数和选项以及它们对应的值。&lt;/p&gt;
&lt;p&gt;考虑以下简单的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;&gt;&gt;&gt; from argparse import ArgumentParser

&gt;&gt;&gt; parser = ArgumentParser()

&gt;&gt;&gt; parser.add_argument(&quot;site&quot;)
_StoreAction(...)

&gt;&gt;&gt; parser.add_argument(&quot;-c&quot;, &quot;--connect&quot;, action=&quot;store_true&quot;)
_StoreTrueAction(...)

&gt;&gt;&gt; args = parser.parse_args([&quot;Real Python&quot;, &quot;-c&quot;])
&gt;&gt;&gt; args
Namespace(site=&apos;Real Python&apos;, connect=True)

&gt;&gt;&gt; args.site
&apos;Real Python&apos;
&gt;&gt;&gt; args.connect
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过在命令行参数解析器上调用 &lt;code&gt;.parse_args()&lt;/code&gt; 方法得到的 &lt;code&gt;Namespace&lt;/code&gt; 对象，你可以使用&lt;strong&gt;点表示法&lt;/strong&gt;(dot notation)访问所有输入参数、选项以及它们对应的值。这样，你就可以检查输入参数和选项的列表，并根据用户在命令行上的选择来执行相应的操作。&lt;/p&gt;
&lt;p&gt;你将在应用程序的主代码中使用这个 &lt;code&gt;Namespace&lt;/code&gt; 对象。这与你在自定义 &lt;code&gt;ls&lt;/code&gt; 命令示例中的 &lt;code&gt;for&lt;/code&gt; 循环下所做的类似。&lt;/p&gt;
&lt;p&gt;到目前为止，你已经了解了创建基于 &lt;code&gt;argparse&lt;/code&gt; 的 CLI 的主要步骤。现在，你可以花些时间学习如何在 Python 中组织和构建 CLI 应用程序的基础知识了。&lt;/p&gt;
&lt;h3 id=&quot;user-content-设置-cli-应用程序的布局和构建系统&quot;&gt;设置 CLI 应用程序的布局和构建系统&lt;/h3&gt;
&lt;p&gt;在继续你的 &lt;code&gt;argparse&lt;/code&gt; 学习之旅之前，你应该暂停一下，思考如何组织你的代码和规划一个 CLI 项目。首先，你应该考虑以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你可以创建模块和包来组织代码。&lt;/li&gt;
&lt;li&gt;你可以将 Python 应用的核心包命名为应用本身的名字。&lt;/li&gt;
&lt;li&gt;你会根据每个 Python 模块的具体内容或功能来命名它们。&lt;/li&gt;
&lt;li&gt;如果你希望某个包可以直接执行，你可以在该 Python 包中添加一个 &lt;code&gt;__main__.py&lt;/code&gt; 模块。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将这些想法铭记于心，并考虑到模型-视图-控制器（MVC）模式是一种有效组织应用程序结构的方法，你在规划 CLI 项目时可以采用以下目录结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hello_cli/
│
├── hello_cli/
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli.py
│   └── model.py
│
├── tests/
│   ├── __init__.py
│   ├── test_cli.py
│   └── test_model.py
│
├── pyproject.toml
├── README.md
└── requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hello_cli/&lt;/code&gt; 目录是项目的根目录。在那里，您将放置以下文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pyproject.toml 是一个 TOML 文件，用于指定项目的构建系统(build system)和其他配置(configurations)。&lt;/li&gt;
&lt;li&gt;README.md 文件提供了项目的描述以及安装和运行应用程序的说明。为你的项目添加一个描述性且详细的 README.md 文件是编程中的最佳实践，特别是如果你打算将项目作为开源解决方案发布的话。&lt;/li&gt;
&lt;li&gt;requirements.txt 是一个常规文件，列出了项目的外部依赖项(external dependencies)。你将使用这个文件，结合 &lt;code&gt;pip&lt;/code&gt; 的 &lt;code&gt;-r&lt;/code&gt; 选项，来自动安装这些依赖项。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来是 &lt;code&gt;hello_cli/&lt;/code&gt; 目录，它包含了应用的核心包，该包包含以下模块：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__init__.py&lt;/code&gt; 文件使得 &lt;code&gt;hello_cli/&lt;/code&gt; 可以作为一个 Python 包被识别。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__main__.py&lt;/code&gt; 文件提供了应用程序的&lt;strong&gt;入口点脚本&lt;/strong&gt;(entry-point script)或可执行文件，这是启动程序的主要入口。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cli.py&lt;/code&gt; 文件为应用提供了命令行界面。在此文件中的代码将扮演基于 MVC 架构中的视图-控制器角色。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.py&lt;/code&gt; 文件包含了支持应用主要功能的代码。这部分代码将在你的 MVC 布局中扮演模型角色。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你还需要一个 &lt;code&gt;tests/&lt;/code&gt; 包，其中包含针对应用程序组件的单元测试文件。在这个具体的项目布局示例中，你有 &lt;code&gt;test_cli.py&lt;/code&gt; 用于检查 CLI 功能的单元测试，以及 &lt;code&gt;test_model.py&lt;/code&gt; 用于检查你的模型代码的单元测试。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 文件允许你定义应用程序的构建系统以及许多其他常规配置。以下是一个如何为你的示例 hello_cli 项目填写此文件的简单示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# pyproject.toml

[build-system]
requires = [&quot;setuptools&gt;=64.0.0&quot;, &quot;wheel&quot;]
build-backend = &quot;setuptools.build_meta&quot;

[project]
name = &quot;hello_cli&quot;
version = &quot;0.0.1&quot;
description = &quot;My awesome Hello CLI application&quot;
readme = &quot;README.md&quot;
authors = [{ name = &quot;Real Python&quot;, email = &quot;info@realpython.com&quot; }]

[project.scripts]
hello_cli = &quot;hello_cli.__main__:main&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;[build-system]&lt;/code&gt; 表头将 &lt;code&gt;setuptools&lt;/code&gt; 设置为应用程序的构建系统，并指定 Python 需要安装哪些依赖项来构建应用程序。&lt;code&gt;[project]&lt;/code&gt; 表头为你的应用提供了通用元数据。这些元数据在你想要将应用发布到 Python 包索引（PyPI）时非常有用。最后， &lt;code&gt;[project.scripts]&lt;/code&gt; 表头定义了你的应用程序的入口点。&lt;/p&gt;
&lt;p&gt;经过这次对 CLI 项目布局和构建的快速探索，你已经准备好继续学习 &lt;code&gt;argparse&lt;/code&gt; 了，特别是如何自定义你的命令行参数解析器。&lt;/p&gt;
&lt;h2 id=&quot;user-content-自定义你的命令行参数解析器&quot;&gt;自定义你的命令行参数解析器&lt;/h2&gt;
&lt;p&gt;在前面的部分中，你已经学习了如何使用 Python 的 &lt;code&gt;argparse&lt;/code&gt; 模块为你的程序或应用实现命令行接口的基础知识。同时，你也了解了如何按照 MVC 模式组织和规划 CLI 应用项目。&lt;/p&gt;
&lt;p&gt;在接下来的部分中，你将更深入地探索 &lt;code&gt;argparse&lt;/code&gt; 的许多其他强大功能。特别是，你将学习如何在 &lt;code&gt;ArgumentParser&lt;/code&gt; 构造函数中使用一些最有用的参数，这将使你能够自定义 CLI 应用的一般行为。&lt;/p&gt;
&lt;h3 id=&quot;user-content-调整程序的-help-和-usage-内容&quot;&gt;调整程序的 Help 和 Usage 内容&lt;/h3&gt;
&lt;p&gt;向 CLI 应用程序的用户提供使用说明和帮助是一种最佳实践，可以通过出色的用户体验 (UX) 让用户更加愉快。在本节中，你将了解如何利用 &lt;code&gt;ArgumentParser&lt;/code&gt; 的一些参数来微调 CLI 应用程序向用户显示帮助和使用消息的方式。你将学习如何：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置程序名称&lt;/li&gt;
&lt;li&gt;定义程序的描述和结束消息&lt;/li&gt;
&lt;li&gt;对参数和选项进行分组显示帮助&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;首先，你将开始设置你的程序名称，并指定该名称在帮助或使用说明消息中的显示方式。&lt;/p&gt;
&lt;h4 id=&quot;user-content-设置程序名称&quot;&gt;设置程序名称&lt;/h4&gt;
&lt;p&gt;默认情况下，&lt;code&gt;argparse&lt;/code&gt; 会使用 &lt;code&gt;sys.argv&lt;/code&gt; 中的第一个值来设置程序的名称。这个第一项包含你刚刚执行的 Python 文件的名称。这个文件名在使用说明消息中看起来会有些奇怪。&lt;/p&gt;
&lt;p&gt;例如，继续使用 &lt;code&gt;-h&lt;/code&gt; 选项运行自定义 &lt;code&gt;ls&lt;/code&gt; 命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py -h
usage: ls.py [-h] [-l] path

positional arguments:
  path

options:
  -h, --help  show this help message and exit
  -l, --long
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令输出中的高亮行显示 &lt;code&gt;argparse&lt;/code&gt; 正在使用文件名 &lt;code&gt;ls.py&lt;/code&gt; 作为程序的名称。这看起来有些奇怪，因为在使用说明消息中，应用名称很少包含文件扩展名。&lt;/p&gt;
&lt;p&gt;幸运的是，你可以使用 &lt;code&gt;prog&lt;/code&gt; 参数来指定你的程序名称，就像下面的代码片段所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v3

import argparse
import datetime
from pathlib import Path

parser = argparse.ArgumentParser(prog=&quot;ls&quot;)

# ...

for entry in target_dir.iterdir():
    print(build_output(entry, long=args.long))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;prog&lt;/code&gt; 参数，你可以指定将在使用说明消息中使用的程序名称。在这个例子中，你使用了字符串 &quot;ls&quot;。现在，继续运行你的应用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py -h
usage: ls [-h] [-l] path

positional arguments:
  path

options:
  -h, --help  show this help message and exit
  -l, --long
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很好！这个输出的第一行中的使用说明消息显示程序名称为 &lt;code&gt;ls&lt;/code&gt;，而不是 &lt;code&gt;ls.py&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;除了设置程序名称外，&lt;code&gt;argparse&lt;/code&gt; 还允许你定义应用的描述和结尾信息。在接下来的部分中，你将学习如何进行这两方面的操作。&lt;/p&gt;
&lt;h4 id=&quot;user-content-定义程序的描述和结语消息&quot;&gt;定义程序的描述和结语消息&lt;/h4&gt;
&lt;p&gt;你还可以为你的应用定义一个通用的描述和一个结尾或结束语。为此，你将分别使用 &lt;code&gt;description&lt;/code&gt; 和 &lt;code&gt;epilog&lt;/code&gt; 参数。接下来，请更新 &lt;code&gt;ls.py&lt;/code&gt; 文件，在 &lt;code&gt;ArgumentParser&lt;/code&gt; 构造函数中添加以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v4

import argparse
import datetime
from pathlib import Path

parser = argparse.ArgumentParser(
    prog=&quot;ls&quot;,
    description=&quot;List the content of a directory&quot;,
    epilog=&quot;Thanks for using %(prog)s! :)&quot;,
)

# ...

for entry in target_dir.iterdir():
    print(build_output(entry, long=args.long))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这次更新中，&lt;code&gt;description&lt;/code&gt; 参数允许你为应用提供一个通用的描述。这个描述将显示在帮助消息的开头。&lt;code&gt;epilog&lt;/code&gt; 参数则允许你定义一些文本作为应用的结尾或结束语。请注意，你可以使用旧式的字符串格式化操作符(&lt;code&gt;%&lt;/code&gt;)将 &lt;code&gt;prog&lt;/code&gt; 参数插入到 &lt;code&gt;epilog&lt;/code&gt; 字符串中。&lt;/p&gt;
&lt;p&gt;如果再次运行该应用程序，你将得到如下输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python ls.py -h
usage: ls [-h] [-l] path

List the content of a directory

positional arguments:
  path

options:
  -h, --help  show this help message and exit
  -l, --long

Thanks for using ls! :)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，输出会在使用消息之后显示描述消息，并在帮助文本末尾显示结语消息。&lt;/p&gt;
&lt;h4 id=&quot;user-content-显示参数和选项的分组帮助&quot;&gt;显示参数和选项的分组帮助&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;帮助分组&lt;/strong&gt;(Help groups)是 &lt;code&gt;argparse&lt;/code&gt; 的另一个有趣特性。它们允许你将相关的命令和参数进行分组，从而帮助你组织应用的帮助消息。要创建这些帮助分组，你将使用 &lt;code&gt;ArgumentParser&lt;/code&gt; 的 &lt;code&gt;.add_argument_group()&lt;/code&gt; 方法。&lt;/p&gt;
&lt;p&gt;作为一个例子，请考虑你自定义的 &lt;code&gt;ls&lt;/code&gt; 命令的以下更新版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v5
# ...

parser = argparse.ArgumentParser(
    prog=&quot;ls&quot;,
    description=&quot;List the content of a directory&quot;,
    epilog=&quot;Thanks for using %(prog)s! :)&quot;,
)

general = parser.add_argument_group(&quot;general output&quot;)
general.add_argument(&quot;path&quot;)

detailed = parser.add_argument_group(&quot;detailed output&quot;)
detailed.add_argument(&quot;-l&quot;, &quot;--long&quot;, action=&quot;store_true&quot;)

args = parser.parse_args()

# ...

for entry in target_dir.iterdir():
    print(build_output(entry, long=args.long))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这次更新中，你为显示一般输出的参数和选项创建了一个帮助分组，并为显示详细输出的参数和选项创建了另一个分组。&lt;/p&gt;
&lt;p&gt;如果你在命令行中使用 &lt;code&gt;-h&lt;/code&gt; 选项运行应用程序，那么你将获得以下输出：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python ls.py -h
usage: ls [-h] [-l] path

List the content of a directory

options:
  -h, --help  show this help message and exit

general output:
  path

detailed output:
  -l, --long

Thanks for using ls! :)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，你的应用的参数和选项在帮助消息中以描述性的标题进行了方便的分组。这个整洁的特性将帮助你为用户提供更多的上下文，并帮助他们更好地理解应用的工作原理。&lt;/p&gt;
&lt;h3 id=&quot;user-content-为参数和选项提供全局设置&quot;&gt;为参数和选项提供全局设置&lt;/h3&gt;
&lt;p&gt;除了自定义使用说明和帮助消息外，&lt;code&gt;ArgumentParser&lt;/code&gt; 还允许你对命令行界面（CLI）应用进行其他一些有趣的调整。这些调整包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为参数和选项定义全局默认值&lt;/li&gt;
&lt;li&gt;从外部文件中加载参数和选项&lt;/li&gt;
&lt;li&gt;允许或禁止选项缩写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有时，你可能需要为你的应用的参数和选项指定一个全局默认值。你可以通过在调用 &lt;code&gt;ArgumentParser&lt;/code&gt; 构造函数时，将默认值传递给 &lt;code&gt;argument_default&lt;/code&gt; 参数来实现这一点（注意：实际上 &lt;code&gt;ArgumentParser&lt;/code&gt; 没有 &lt;code&gt;argument_default&lt;/code&gt; 这个参数，但这里是为了说明可以全局设置默认值的概念。在实际应用中，你可能需要为每个参数单独设置默认值）。&lt;/p&gt;
&lt;p&gt;这个特性可能并不常用，因为参数和选项通常具有不同的数据类型或意义，很难找到一个满足所有需求的值。&lt;/p&gt;
&lt;p&gt;然而，&lt;code&gt;argument_default&lt;/code&gt;(尽管 &lt;code&gt;ArgumentParser&lt;/code&gt; 并没有直接提供这个参数，但这里是为了说明概念）的一个常见用例是当你想要避免将参数和选项添加到 &lt;code&gt;Namespace&lt;/code&gt; 对象中。在这种情况下，你可以使用 &lt;code&gt;SUPPRESS&lt;/code&gt; 常量作为默认值。这个默认值将确保只有命令行中提供的参数和选项才会被存储在 &lt;code&gt;arguments&lt;/code&gt; 的 &lt;code&gt;Namespace&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;作为一个例子，请继续修改你的自定义 &lt;code&gt;ls&lt;/code&gt; 命令，如下面的代码片段所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ls.py v6

import argparse
import datetime
from pathlib import Path

parser = argparse.ArgumentParser(
    prog=&quot;ls&quot;,
    description=&quot;List the content of a directory&quot;,
    epilog=&quot;Thanks for using %(prog)s! :)&quot;,
    argument_default=argparse.SUPPRESS,
)

# ...

for entry in target_dir.iterdir():
    try:
        long = args.long
    except AttributeError:
        long = False
    print(build_output(entry, long=long))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过将 &lt;code&gt;SUPPRESS&lt;/code&gt; 传递给 &lt;code&gt;ArgumentParser&lt;/code&gt; 构造函数，你可以防止未提供的参数被存储在 &lt;code&gt;argparse.Namespace&lt;/code&gt; 对象中。这就是为什么在调用 &lt;code&gt;build_output()&lt;/code&gt; 之前，你需要检查 &lt;code&gt;-l&lt;/code&gt; 或 &lt;code&gt;--long&lt;/code&gt; 选项是否实际被传递了。否则，你的代码会因为 &lt;code&gt;args&lt;/code&gt; 中不存在 &lt;code&gt;long&lt;/code&gt; 属性而引发 &lt;code&gt;AttributeError&lt;/code&gt; 错误。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ArgumentParser&lt;/code&gt; 的另一个酷炫功能是允许你从外部文件中加载参数值。当你有一个具有冗长或复杂的命令行结构的应用，并希望自动化加载参数值的过程时，这个功能就非常有用。&lt;/p&gt;
&lt;p&gt;在这种情况下，你可以将参数值存储在一个外部文件中，并让你的程序从该文件中加载它们。为了尝试这个功能，请继续创建一个简单的命令行界面（CLI）应用，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# fromfile.py

import argparse

parser = argparse.ArgumentParser(fromfile_prefix_chars=&quot;@&quot;)

parser.add_argument(&quot;one&quot;)
parser.add_argument(&quot;two&quot;)
parser.add_argument(&quot;three&quot;)

args = parser.parse_args()

print(args)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里，你向 &lt;code&gt;ArgumentParser&lt;/code&gt; 的 &lt;code&gt;fromfile_prefix_chars&lt;/code&gt; 参数传递 &lt;code&gt;@&lt;/code&gt; 符号。然后，你创建了三个必须在命令行中提供的必需参数。&lt;/p&gt;
&lt;p&gt;现在，假设你经常使用此应用程序，并且总是使用相同的一组参数值。为了简化和优化你的工作流程，你可以创建一个文件，其中包含所有必需参数的适当值，每个参数占一行，就像下面的 &lt;code&gt;args.txt&lt;/code&gt; 文件一样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;first
second
third
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了这个文件，您现在可以调用您的程序并指示它从 &lt;code&gt;args.txt&lt;/code&gt; 文件加载值，如以下命令运行所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python fromfile.py @args.txt
Namespace(one=&apos;first&apos;, two=&apos;second&apos;, three=&apos;third&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在此命令的输出中，你可以看到 &lt;code&gt;argparse&lt;/code&gt; 已经读取了 &lt;code&gt;args.txt&lt;/code&gt; 的内容，并顺序地将值分配给了 &lt;code&gt;fromfile.py&lt;/code&gt; 程序的每个参数。所有参数及其值都已成功存储在 &lt;code&gt;Namespace&lt;/code&gt; 对象中。&lt;/p&gt;
&lt;p&gt;接受缩写选项名称的能力是 &lt;code&gt;argparse&lt;/code&gt; 命令行界面（CLI）的另一个酷炫特性。这个特性是默认启用的，当你的程序具有长选项名称时非常有用。例如，考虑以下程序，它会在命令行下打印出你在 &lt;code&gt;--argument-with-a-long-name&lt;/code&gt; 选项后指定的值：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# abbreviate.py

import argparse

parser = argparse.ArgumentParser()

parser.add_argument(&quot;--argument-with-a-long-name&quot;)

args = parser.parse_args()

print(args.argument_with_a_long_name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个程序会打印出你在 &lt;code&gt;--argument-with-a-long-name&lt;/code&gt; 选项后传入的任何参数值。现在，请继续运行以下命令来检查 Python 的 &lt;code&gt;argparse&lt;/code&gt; 模块如何处理这些缩写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python abbreviate.py --argument-with-a-long-name 42
42

$ python abbreviate.py --argument 42
42

$ python abbreviate.py --a 42
42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些示例展示了如何缩写 &lt;code&gt;--argument-with-a-long-name&lt;/code&gt; 选项的名称，而应用程序仍能正常工作。此功能是默认启用的。如果你希望禁用它并禁止缩写，那么可以在 &lt;code&gt;ArgumentParser&lt;/code&gt; 中使用 &lt;code&gt;allow_abbrev&lt;/code&gt; 参数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# abbreviate.py

import argparse

parser = argparse.ArgumentParser(allow_abbrev=False)

parser.add_argument(&quot;--argument-with-a-long-name&quot;)

args = parser.parse_args()

print(args.argument_with_a_long_name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;code&gt;allow_abbrev&lt;/code&gt; 设置为 &lt;code&gt;False&lt;/code&gt; 会禁用命令行选项中的缩写。从这一点开始，你将需要为程序提供完整的选项名称才能正确工作。否则，你会收到一个错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ python abbreviate.py --argument-with-a-long-name 42
42

$ python abbreviate.py --argument 42
usage: abbreviate.py [-h] [--argument-with-a-long-name ...]
abbreviate.py: error: unrecognized arguments: --argument 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二个示例中的错误消息告诉你 &lt;code&gt;--argument&lt;/code&gt; 选项没有被识别为有效的选项。要使用该选项，你需要提供它的完整名称。&lt;/p&gt;
&lt;h2 id=&quot;user-content-微调你的命令行参数和选项&quot;&gt;微调你的命令行参数和选项&lt;/h2&gt;
&lt;p&gt;到目前为止，你已经学习了如何定制 &lt;code&gt;ArgumentParser&lt;/code&gt; 类的多个功能，以改善你的命令行界面（CLI）的用户体验。现在，你知道了如何调整你的应用程序的使用说明和帮助信息，以及如何微调命令行参数和选项的一些全局方面。&lt;/p&gt;
&lt;p&gt;在本节中，你将学习如何定制你的 CLI 的命令行参数和选项的其他几个功能。在这种情况下，你将使用 &lt;code&gt;.add_argument()&lt;/code&gt; 方法及其一些最相关的参数，包括 &lt;code&gt;action&lt;/code&gt;、&lt;code&gt;type&lt;/code&gt;、&lt;code&gt;nargs&lt;/code&gt;、&lt;code&gt;default&lt;/code&gt;、&lt;code&gt;help&lt;/code&gt; 等。&lt;/p&gt;
&lt;h3 id=&quot;user-content-设置-option-背后的-action&quot;&gt;设置 Option 背后的 Action&lt;/h3&gt;
&lt;p&gt;当你向命令行界面添加一个选项或标志时，通常需要定义如何将选项的值存储在调用 &lt;code&gt;.parse_args()&lt;/code&gt; 后得到的 &lt;code&gt;Namespace&lt;/code&gt; 对象中。为此，你会使用 &lt;code&gt;.add_argument()&lt;/code&gt; 的 &lt;code&gt;action&lt;/code&gt; 参数。&lt;code&gt;action&lt;/code&gt; 参数的默认值为 &quot;store&quot;，意味着提供的选项值将原样存储在 &lt;code&gt;Namespace&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;action&lt;/code&gt; 参数可以接受几个可能的值。以下是这些可能值的列表及其含义：&lt;/p&gt;</content:encoded><category>Python</category></item><item><title>Pandas 处理 Excel 常用方法技巧</title><link>https://shenweiyan.github.io/posts/5/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/5/</guid><description>在使用 Pandas 前需要学习了解一下 Series 和 DataFrame 的基本数据结构和索引的相关概念，之后就可以练习基本的 Excel操作。Pandas 读取一个 Excel 文件后会将其转化为 DataFrame 对象，每一列或行就是一个 Series 对象。这里我们看下如何对一个 excel 进行读写，以及 Sheet、行列、表头处理的一些常用技巧。 如果您想读取 Excel 表格中…</description><pubDate>Thu, 29 Oct 2026 02:50:07 GMT</pubDate><content:encoded>&lt;p&gt;在使用 Pandas 前需要学习了解一下 Series 和 DataFrame 的基本数据结构和索引的相关概念，之后就可以练习基本的 Excel操作。Pandas 读取一个 Excel 文件后会将其转化为 DataFrame 对象，每一列或行就是一个 Series 对象。这里我们看下如何对一个 excel 进行读写，以及 Sheet、行列、表头处理的一些常用技巧。&lt;/p&gt;
&lt;h2 id=&quot;user-content-读取-excel&quot;&gt;读取 Excel&lt;/h2&gt;
&lt;p&gt;如果您想读取 Excel 表格中的数据，可以使用 &lt;code&gt;read_excel()&lt;/code&gt; 方法，其语法格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pd.read_excel(io, sheet_name=0, header=0, names=None, index_col=None,
              usecols=None, squeeze=False,dtype=None, engine=None,
              converters=None, true_values=None, false_values=None,
              skiprows=None, nrows=None, na_values=None, parse_dates=False,
              date_parser=None, thousands=None, comment=None, skipfooter=0,
              convert_float=True, **kwds)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下表对常用参数做了说明：&lt;/p&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;参数名称&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;io&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;表示 Excel 文件的存储路径。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;sheet_name&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;要读取的工作表名称，默认0，即读取第一个工作表作为 DataFrame(一定要加&lt;code&gt;sheet_name=None&lt;/code&gt;，才能读取出所有的 sheet，否则默认读取第一个 sheet)。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;header&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;指定作为列名的行，默认0，即取第一行的值为列名；若数据不包含列名，则设定 header = None。若将其设置为 header=2，则表示将前两行作为多重索引。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;names&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;一般适用于Excel缺少列名，或者需要重新定义列名的情况；names的长度必须等于Excel表格列的长度，否则会报错。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;index_col&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;用做行索引的列，可以是工作表的列名称，如 index_col = &apos;列名&apos;，也可以是整数或者列表。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;usecols&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;int或list类型，默认为None，表示需要读取所有列。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;squeeze&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;boolean，默认为False，如果解析的数据只包含一列，则返回一个Series。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;converters&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;规定每一列的数据类型。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;skiprows&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;接受一个列表，表示跳过指定行数的数据，从头部第一行开始。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;nrows&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;需要读取的行数。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;skipfooter&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;接受一个列表，省略指定行数的数据，从尾部最后一行开始。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;示例如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd

# 读取所有Sheet
df = pd.read_excel(&apos;example.xlsx&apos;, sheet_name=None)

# 读取第一个、第二个和名为&quot;Sheet5&quot;的工作表作为 DataFrame 的字典
df = pd.read_excel(&apos;example.xlsx&apos;, sheet_name=[0, 1, &quot;Sheet5&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;user-content-获取行数和列数&quot;&gt;获取行数和列数&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
 
df = pd.read_excel(&apos;example.xlsx&apos;)
# 行索引
print(df.index)  
# RangeIndex(start=0, stop=3747, step=1)

# 输出元祖,分别为行数和列数,默认第一行是表头不算行数
print(df.shape) 
# (3747, 4)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;user-content-获取表头&quot;&gt;获取表头&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;read_excel&lt;/code&gt; 默认是把 excel 的第一行当成表头。注意：如果 &lt;code&gt;read_excel&lt;/code&gt; 的 &lt;code&gt;sheet_name=None&lt;/code&gt;，读取的是所有 excel 的 sheet_name(key) 和 sheet_values(values) 组成的字典，&lt;code&gt;df.keys()&lt;/code&gt; 的结果是所有 sheet_name，即名字(字典的键)。&lt;/p&gt;
&lt;h3 id=&quot;user-content-获取第一个-sheet&quot;&gt;获取第一个 sheet&lt;/h3&gt;
&lt;p&gt;这时候 &lt;code&gt;df.keys()&lt;/code&gt; 和 &lt;code&gt;df.columns&lt;/code&gt; 的结果是一样的，都是第一个 sheet 的表头。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
 
df = pd.read_excel(&apos;input.xlsx&apos;)
print(df.keys())
print(&apos;---------------&apos;)
print(df.columns)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;user-content-获取所有-sheet&quot;&gt;获取所有 sheet&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pandas as pd
 
# 参数为 None 代表读取所有 sheet
df = pd.read_excel(&apos;input.xlsx&apos;,sheet_name=None)

# 获取所有sheet名字, 如果read_excel参数不是None, 则df.keys()为表头
sheet_names = list(df.keys())
print(sheet_names)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;user-content-参考资料&quot;&gt;参考资料&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;老董，《&lt;a href=&quot;https://www.python66.com/pandasshujufenxi/268.html&quot;&gt;pandas获取excel的行数,列数,表头,sheet,前后行等数据&lt;/a&gt;》，&lt;a href=&quot;https://www.python66.com/&quot;&gt;Python编程网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;《&lt;a href=&quot;https://c.biancheng.net/pandas/excel.html&quot;&gt;Pandas Excel读写操作详解&lt;/a&gt;》，&lt;a href=&quot;https://c.biancheng.net/&quot;&gt;C语言中文网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;《&lt;a href=&quot;https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html&quot;&gt;pandas.read_excel — pandas 2.1.4 documentation&lt;/a&gt;》，&lt;a href=&quot;https://pandas.pydata.org/docs/index.html&quot;&gt;pandas documentation — pandas 2.1.4 documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>Python</category><category>Pandas</category></item><item><title>不要使用 requirements.txt</title><link>https://shenweiyan.github.io/posts/4/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/4/</guid><description>原文：Do not use requirements.txt 你是否在用 Python 开发后端服务？我有两条建议： 不要使用 pip 和 requirements.txt 来管理 Python 依赖。它们缺乏一些应该内置的关键功能。 改用 Poetry。 对我来说，第一条建议毋庸置疑。第二条则更具有暂时性：Poetry 是一个很好的选择，但并非唯一值得考虑的选择。 我将在下面进行解释。 请注意：…</description><pubDate>Thu, 29 Oct 2026 02:48:42 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;原文：&lt;a href=&quot;https://quanttype.net/posts/2023-10-31-do-not-use-requirements.txt.html&quot;&gt;Do not use requirements.txt&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你是否在用 Python 开发后端服务？我有两条建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要使用 &lt;code&gt;pip&lt;/code&gt; 和 &lt;code&gt;requirements.txt&lt;/code&gt; 来管理 Python 依赖。它们缺乏一些应该内置的关键功能。&lt;/li&gt;
&lt;li&gt;改用 &lt;a href=&quot;https://python-poetry.org/&quot;&gt;Poetry&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对我来说，第一条建议毋庸置疑。第二条则更具有暂时性：Poetry 是一个很好的选择，但并非唯一值得考虑的选择。&lt;/p&gt;
&lt;p&gt;我将在下面进行解释。&lt;/p&gt;
&lt;p&gt;请注意：如果你使用 Python 做其他事情而不是构建后端服务，那么本文中的建议可能并不适用于你。例如，如果你是&lt;a href=&quot;https://gregoryszorc.com/blog/2023/10/30/my-user-experience-porting-off-setup.py/&quot;&gt;一个正在迁移 &lt;code&gt;setup.py&lt;/code&gt; 的库开发者&lt;/a&gt;，Poetry 并不明显是一个完美的选择。&lt;/p&gt;
&lt;h2 id=&quot;user-content-pip-缺失的功能&quot;&gt;PIP 缺失的功能&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pypi.org/project/pip/&quot;&gt;pip&lt;/a&gt; 是一个工具，你可以用它从 &lt;a href=&quot;https://pypi.org/&quot;&gt;The Python Package Index (PyPI)&lt;/a&gt; 中安装软件包。它随 Python 一起安装，如果你是 Python 开发者，你可能已经多次使用过它。&lt;/p&gt;
&lt;p&gt;管理 Python 项目依赖的传统方式是将它们列在一个名为 &lt;code&gt;requirements.txt&lt;/code&gt; 的文件中，并使用 &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; 进行安装。然而，&lt;code&gt;pip&lt;/code&gt; 被设计成一个软件包安装工具，而不是一个功能齐全的项目工作流工具。&lt;strong&gt;pip 缺乏两个关键功能，即依赖的锁定文件 (dependency lockfiles) 和虚拟环境的自动管理(automatic management of virtualenvs)。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;user-content-依赖锁定文件&quot;&gt;依赖锁定文件&lt;/h2&gt;
&lt;p&gt;如果你希望在所有环境中（比如你的笔记本电脑、持续集成(CI)、生产环境）获得相同的行为，你需要锁定你的依赖项及其传递依赖的版本。你可以在 &lt;code&gt;requirements.txt&lt;/code&gt; 中锁定你直接依赖的版本，例如，使用 &lt;code&gt;requests==2.31.0&lt;/code&gt; 而不是 &lt;code&gt;requests&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然而，pip 不会锁定传递依赖的版本。这可以通过使用 &lt;a href=&quot;https://github.com/jazzband/pip-tools&quot;&gt;pip-tools&lt;/a&gt; 来解决，将 &lt;code&gt;requirements.txt&lt;/code&gt; 扩展成一个列出完整依赖图的文件，包括准确版本和构件的校验和(checksums for the artifacts)。pip-tools 很不错，但你需要自行设置并弄清楚它如何适应你的工作流程。&lt;/p&gt;
&lt;p&gt;在其他编程语言中，这个功能是基本要求的 - 例如，npm 多年来一直有 &lt;code&gt;package-lock.json&lt;/code&gt;，Cargo 也有 &lt;code&gt;Cargo.lock&lt;/code&gt;。这个功能实在应该是一个项目工作流工具中的内置功能。&lt;/p&gt;
&lt;h2 id=&quot;user-content-虚拟环境的自动管理&quot;&gt;虚拟环境的自动管理&lt;/h2&gt;
&lt;p&gt;在 Python 中创建隔离环境的方式是使用 &lt;a href=&quot;https://docs.python.org/3/library/venv.html&quot;&gt;virtualenvs&lt;/a&gt;。传统上，你需要手动管理它们：通过一个 shell 命令创建一个（比如 &lt;code&gt;python -m venv example&lt;/code&gt; 来创建名为 &lt;code&gt;example&lt;/code&gt; 的虚拟环境），当你想要使用它时，需要用另一个 shell 命令来激活它。&lt;/p&gt;
&lt;p&gt;这容易出错：忘记激活虚拟环境或者激活错误的虚拟环境是常见的错误。有一堆的解决方法。例如，你可以使用 &lt;a href=&quot;https://github.com/pyenv/pyenv-virtualenv&quot;&gt;pyenv-virtualenv&lt;/a&gt;，在进入项目目录时让你的 shell 自动激活一个虚拟环境。&lt;a href=&quot;https://github.com/direnv/direnv/wiki/Python&quot;&gt;direnv&lt;/a&gt; 也可以做到。&lt;/p&gt;
&lt;p&gt;同样，这也应该成为工作流工具中的一个内置功能。你不应该需要将多个工具粘合在一起。你不会听到 npm 或 Cargo 用户在虚拟环境上遇到问题的。&lt;/p&gt;
&lt;h2 id=&quot;user-content-poetry-及其他选择&quot;&gt;Poetry 及其他选择&lt;/h2&gt;
&lt;p&gt;幸运的是，许多人已经意识到这些问题并努力解决它们。不太幸运的是，这导致了大量的 Python 项目工作流工具涌现。那么该如何选择呢？&lt;/p&gt;
&lt;p&gt;我的建议是：&lt;strong&gt;选择 &lt;a href=&quot;https://python-poetry.org/docs/&quot;&gt;Poetry&lt;/a&gt;&lt;/strong&gt;。它有锁定文件，有虚拟环境管理，而且很受欢迎且在积极开发中。根据我的经验，它并不完美，但是它起作用。&lt;/p&gt;
&lt;p&gt;你也可以考虑 &lt;a href=&quot;https://hatch.pypa.io/latest/&quot;&gt;Hatch&lt;/a&gt; 或 &lt;a href=&quot;https://github.com/pdm-project/pdm&quot;&gt;PDM&lt;/a&gt;。它们与 Poetry 相似。我自己没有使用过它们，但我听说其他人成功地使用了它们。Hatch 似乎在库作者 (library authors) 中特别受欢迎。&lt;/p&gt;
&lt;p&gt;如果你正在寻找一个更强大的选项，可以处理多个子项目，&lt;a href=&quot;https://www.pantsbuild.org/&quot;&gt;Pants 构建系统&lt;/a&gt;在 Python 支持方面做得很好。然而，它的学习曲线相对陡峭。&lt;/p&gt;
&lt;p&gt;最后，如果你正在寻找一个类似 rustup 那样可以为你安装 Python 的解决方案，那么有 &lt;a href=&quot;https://github.com/mitsuhiko/rye&quot;&gt;rye&lt;/a&gt;。它是新的实验性工具，但也许它对你来说是正确的选择？&lt;/p&gt;
&lt;h2 id=&quot;user-content-哪个是权威的工作流工具&quot;&gt;哪个是权威的工作流工具？&lt;/h2&gt;
&lt;p&gt;如果 Python 自带一个权威的项目工作流工具会很好。很多人希望 pip 成为这样一个工具。Node.js 自带 npm，Rust 自带 Cargo，那么为什么 Python 就不能有一个呢？为什么会有这么多竞争的选择呢？&lt;/p&gt;
&lt;p&gt;据我所知，最大的障碍是，由于 Python 被广泛使用且用于许多不同的用例，制定一个通用的官方解决方案是困难且缓慢的（并且资金不足的）工作。另外，也不清楚 pip 是否适合这些功能。&lt;/p&gt;
&lt;p&gt;如果你想了解更多信息，请阅读和听取这些与我不同、深度参与 Python 社区的人的意见：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stargirl (Thea Flowers) on Fediverse：《&lt;a href=&quot;https://hachyderm.io/@stargirl/109697057391904145&quot;&gt;所以你想解决 Python 打包问题：一个实用指南&lt;/a&gt;》&lt;/li&gt;
&lt;li&gt;Pradyun Gedam：《&lt;a href=&quot;https://pradyunsg.me/blog/2023/01/21/thoughts-on-python-packaging/&quot;&gt;关于 Python 打包生态系统的思考&lt;/a&gt;》&lt;/li&gt;
&lt;li&gt;Talk Python to Me (podcast)：《&lt;a href=&quot;https://talkpython.fm/episodes/show/406/reimagining-pythons-packaging-workflows&quot;&gt;重新构想 Python 的打包工作流程&lt;/a&gt;》&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;user-content-关于-clojure&quot;&gt;关于 Clojure&lt;/h2&gt;
&lt;p&gt;阅读我的博客的 Clojure 开发者可能会问：嘿，Clojure 怎么样？为什么我们没有锁定文件呢？这是一个很好的问题！&lt;/p&gt;
&lt;p&gt;Clojure 社区通过始终使用明确的版本而不是版本范围来解决了这个问题，即使在库中也是如此。版本描述实际上支持范围，但没有人会使用它们。这样，只要版本解析算法稳定，你总是会得到相同的版本。&lt;/p&gt;
&lt;p&gt;理论上，传递依赖项版本不匹配可能是一个问题，但 Clojure 支持一种编码风格，很少引起问题。&lt;/p&gt;
&lt;p&gt;相比之下，在 Python 和 Node.js 社区，通常期望库列出其依赖项的版本范围，而软件包管理工具会抱怨版本不匹配的问题。&lt;/p&gt;</content:encoded><category>Python</category></item><item><title>MkDocs 的缓慢崩溃</title><link>https://shenweiyan.github.io/posts/3/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/3/</guid><description>从 2023 年 7 月开始，个人的 博客站点 从 Hugo 换成了 Material for MkDocs 并在 2026 年换成了 MaterialX，但始终都没有脱离过 MkDocs 的框架。 作为使用者，和 MkDocs 生态关注者，现如今 MkDocs 生态系统分崩离析，给大家分享一篇来自 Florian Maas 在 2026 年 3 月 22 写的《The Slow Collapse…</description><pubDate>Mon, 26 Oct 2026 06:06:34 GMT</pubDate><content:encoded>&lt;p&gt;从 2023 年 7 月开始，个人的 &lt;a href=&quot;https://shenwy.com&quot;&gt;博客站点&lt;/a&gt; 从 Hugo 换成了 Material for MkDocs 并在 2026 年换成了 MaterialX，但始终都没有脱离过 MkDocs 的框架。&lt;/p&gt;
&lt;p&gt;作为使用者，和 MkDocs 生态关注者，现如今 MkDocs 生态系统分崩离析，给大家分享一篇来自 Florian Maas 在 2026 年 3 月 22 写的《&lt;a href=&quot;https://fpgmaas.com/blog/collapse-of-mkdocs/&quot;&gt;The Slow Collapse of MkDocs&lt;/a&gt;》文章，看看性格冲突、创始人缺席以及备受争议的重新设计如何导致 Python 最受欢迎的项目之一分崩离析。&lt;/p&gt;

&lt;hr&gt;
&lt;p&gt;2026 年 3 月 9 日， &lt;a href=&quot;https://github.com/mkdocs/mkdocs&quot; title=&quot;MkDocs&quot;&gt;MkDocs&lt;/a&gt; 的一位前维护者&lt;a href=&quot;https://github.com/orgs/ProperDocs/discussions/1&quot;&gt;接管&lt;/a&gt;了 PyPI 代码库的控制权，并撤销了原作者的权限。原作者迅速作出回应：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/collapse-mkdocs-1.png&quot; alt=&quot;collapse-mkdocs-1&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后他们提交了一份 &lt;a href=&quot;https://github.com/pypi/support/issues/9671&quot; title=&quot;PyPI 支持工单&quot;&gt;PyPI 支持工单&lt;/a&gt; 以重新获得控制权，但在工单得到处理之前，问题就已得到了解决。&lt;/p&gt;
&lt;p&gt;MkDocs 为超过 &lt;a href=&quot;https://github.com/mkdocs/mkdocs/network/dependents&quot; title=&quot;90,000&quot;&gt;90,000&lt;/a&gt; 个 GitHub 项目提供文档支持，其中大多数项目都依赖于由 &lt;code&gt;@squidfunk&lt;/code&gt; 开发的 &lt;a href=&quot;https://github.com/squidfunk/mkdocs-material&quot;&gt;Material for MkDocs&lt;/a&gt; 主题，该主题非常受欢迎，其获得的星标数甚至超过了 MkDocs 本身。&lt;/p&gt;
&lt;p&gt;PyPI 被接管事件不过是更深层次问题的一个最为显眼的症状表现。如今，任何使用 Material for MkDocs 的人都会在终端中看到以下警告信息：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/mkdocs-2-warning.png&quot; alt=&quot;mkdocs-2-warning&quot;&gt;&lt;/p&gt;
&lt;p&gt;MkDocs 项目已经超过 18 个月没有进行任何实质性的开发。Material for MkDocs 目前处于&lt;a href=&quot;https://github.com/squidfunk/mkdocs-material/releases/tag/9.7.0&quot; title=&quot;维护模式&quot;&gt;维护模式&lt;/a&gt;。而曾经两大主导工具携手共进的局面已不复存在，如今有多款工具竞相涌现，试图取而代之：如 &lt;a href=&quot;https://github.com/ProperDocs/properdocs&quot; title=&quot;ProperDocs&quot;&gt;ProperDocs&lt;/a&gt;、&lt;a href=&quot;https://github.com/jaywhj/mkdocs-materialx&quot; title=&quot;MaterialX&quot;&gt;MaterialX&lt;/a&gt;（&quot;新一代的 mkdocs-material&quot;）以及 &lt;a href=&quot;https://github.com/zensical/zensical&quot; title=&quot;Zensical&quot;&gt;Zensical&lt;/a&gt;（&quot;由 Material for MkDocs 开发者打造的现代化静态网站生成器&quot;）。&lt;/p&gt;
&lt;p&gt;我们怎么会走到这一步？要了解事情的全貌，我们需要稍微回顾一下过去 ……&lt;/p&gt;
&lt;h2 id=&quot;user-content-2014年1月11日---mkdocs-的诞生&quot;&gt;2014年1月11日 - MkDocs 的诞生&lt;/h2&gt;
&lt;p&gt;2014 年 1 月 11 日，Mia Kimberly Christie（ &lt;code&gt;@lovelydinosaur&lt;/code&gt; ）提交了第一个代码 ，也就是后来的 MkDocs，并附上了 &quot;Hell yeah&quot; 的消息。接下来的几个月里，提交数量激增。&lt;/p&gt;
&lt;p&gt;这种活跃期并没有持续太久。到 2014 年年中，他们在代码库中的活动就完全停止了。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-commits-2014.png&quot; alt=&quot;lovelydinosaur-commits-2014&quot;&gt;&lt;/p&gt;
&lt;p&gt;其他开发者也加入了这个项目，比如 &lt;code&gt;@d0ugal&lt;/code&gt; 和 &lt;code&gt;@waylan&lt;/code&gt;。&lt;code&gt;@lovelydinosaur&lt;/code&gt; 此后便不再活跃，而在 2016 年 7 月至 2021 年 7 月期间，&lt;code&gt;@waylan&lt;/code&gt; 似乎是该项目的唯一维护者。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/waylan-commits.png&quot; alt=&quot;waylan-commits&quot;&gt;&lt;/p&gt;
&lt;p&gt;该项目越来越受欢迎，到 2022 年年中，该项目在 GitHub 上积累了超过 &lt;a href=&quot;https://www.star-history.com/?repos=mkdocs%2Fmkdocs&amp;#x26;type=date&amp;#x26;legend=top-left&quot; title=&quot;14,000&quot;&gt;14,000&lt;/a&gt; 个星标 。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2020年4月27日---oprypin-加入了该项目&quot;&gt;2020年4月27日 - oprypin 加入了该项目&lt;/h2&gt;
&lt;p&gt;2020 年 4 月 27 日，新贡献者 &lt;code&gt;@oprypin&lt;/code&gt; &lt;a href=&quot;https://github.com/mkdocs/mkdocs/pull/2081&quot; title=&quot;首次为该项目做出贡献&quot;&gt;首次为该项目做出贡献&lt;/a&gt;：提交了一个 bug 修复方案。&lt;code&gt;@waylan&lt;/code&gt; 同意了这些更改,并合并了 PR。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/waylan-20200427.png&quot; alt=&quot;waylan-20200427&quot;&gt;&lt;/p&gt;
&lt;p&gt;在首次提交贡献后，&lt;code&gt;@oprypin&lt;/code&gt; 有大约半年时间未参与该项目，之后他又进入了一段&lt;a href=&quot;https://github.com/mkdocs/mkdocs/pulls?page=8&amp;#x26;q=is%3Apr+is%3Amerged+author%3Aoprypin&quot; title=&quot;长时间的高频活跃期&quot;&gt;长时间的高频活跃期&lt;/a&gt;；修复 bug、添加新功能等等，不一而足。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 和 &lt;code&gt;@waylan&lt;/code&gt; 之间似乎存在一些摩擦。例如，&lt;code&gt;@waylan&lt;/code&gt; 就不同意 &lt;code&gt;@oprypin&lt;/code&gt; 提出的一个 &lt;a href=&quot;https://github.com/mkdocs/mkdocs/pull/2272&quot; title=&quot;潜在的性能&quot;&gt;潜在的性能&lt;/a&gt; 改进方案：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/waylan-oprypin-2104.png&quot; alt=&quot;waylan-oprypin-2104&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;user-content-2021年5月16日---waylan-下台&quot;&gt;2021年5月16日 - waylan 下台&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 的不满情绪日益加剧，最终他发起了一个题为 &quot;关于 MkDocs 维护工作的担忧（Concerns about maintainership of MkDocs）&quot; 的&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/2410&quot;&gt;讨论帖&lt;/a&gt;。在这篇长文中，他指责 &lt;code&gt;@waylan&lt;/code&gt; 对项目进行独断专行式的管控，且在没有技术依据的情况下拒绝他人贡献，并提出了另一种维护模式，以减轻单一维护者的责任负担。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-210516.png&quot; alt=&quot;oprypin-210516&quot;&gt;&lt;/p&gt;
&lt;p&gt;他将该 &lt;a href=&quot;https://github.com/oprypin/mkdocs/discussions/2#discussioncomment-745295&quot;&gt;帖子的副本&lt;/a&gt; 发布到他自己 fork 的仓库中。两个帖子均未引发过多讨论，但普遍共识是，尽管事实与观点难以分辨，但该帖子过于针对个人，项目原作者（且基本已不再活跃）也提到了这一点：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-210517.png&quot; alt=&quot;lovelydinosaur-210517&quot;&gt;&lt;/p&gt;
&lt;p&gt;就在 &lt;code&gt;@oprypin&lt;/code&gt; 发起讨论的同一天，&lt;code&gt;@waylan&lt;/code&gt; 宣布退出该项目：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/waylan-210517.png&quot; alt=&quot;waylan-210517&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;user-content-2021年6月7日---oprypin-接管&quot;&gt;2021年6月7日 - oprypin 接管&lt;/h2&gt;
&lt;p&gt;由于 &lt;code&gt;@waylan&lt;/code&gt; 已不再担任项目维护者，因此需要新的维护者。&lt;code&gt;@lovelydinosaur&lt;/code&gt; 发起了一项讨论，征求关于维护者人选的建议。&lt;code&gt;@oprypin&lt;/code&gt; 立即表示他愿意接手项目的维护工作。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-210607.png&quot; alt=&quot;oprypin-210607&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 并没有立即接受他的提议，但 &lt;code&gt;@oprypin&lt;/code&gt; 希望继续开发这个项目，所以几周后他又回到了这个帖子：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-210621.png&quot; alt=&quot;oprypin-210621&quot;&gt;&lt;/p&gt;
&lt;p&gt;一段时间后，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 似乎不太愿意授予 &lt;code&gt;@oprypin&lt;/code&gt; 项目写入权限，且回复迟缓，此时 Material for MkDocs 的原作者兼维护者 &lt;code&gt;@squidfunk&lt;/code&gt; 也加入了讨论。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/squidfunk-210703.png&quot; alt=&quot;squidfunk-210703&quot;&gt;&lt;/p&gt;
&lt;p&gt;之后，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 邀请了一群维护者加入 MkDocs：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-210705.png&quot; alt=&quot;lovelydinosaur-210705&quot;&gt;&lt;/p&gt;
&lt;p&gt;大约一年后，&lt;code&gt;@oprypin&lt;/code&gt; 请求提升对 Github 仓库和 PyPI 项目的权限：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-220602.png&quot; alt=&quot;oprypin-220602&quot;&gt;&lt;/p&gt;
&lt;p&gt;获得所有者权限后，&lt;code&gt;@oprypin&lt;/code&gt; 成为 MkDocs 的主要维护者。在接下来的两年里，他与其他维护者和贡献者一起稳步推进项目。修复 bug、更新依赖项、添加新功能，项目持续向前发展。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-commits-2024.png&quot; alt=&quot;oprypin-commits-2024&quot;&gt;&lt;/p&gt;
&lt;p&gt;请注意，由于 &lt;code&gt;@oprypin&lt;/code&gt; 目前已被禁止参与该项目，因此无法在 MkDocs 存储库中找到上述图表中的数据，但仍然可以在 MkDocs 的分支中找到，例如&lt;a href=&quot;https://github.com/ProperDocs/properdocs/graphs/contributors?from=09%2F04%2F2020&amp;#x26;to=12%2F05%2F2025&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年2月25日---紧张局势公开爆发&quot;&gt;2024年2月25日 - 紧张局势公开爆发&lt;/h2&gt;
&lt;p&gt;一次常规的&lt;a href=&quot;https://github.com/mkdocs/mkdocs/issues/3579&quot; title=&quot;文档问题&quot;&gt;文档问题&lt;/a&gt;讨论，因 &lt;code&gt;@squidfunk&lt;/code&gt; 在帖子中指责 Read the Docs 从 MkDocs 项目中获利却未回馈社区而升级，演变成了另一回事。&lt;code&gt;@oprypin&lt;/code&gt; 趁机表态：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-240226.png&quot; alt=&quot;oprypin-240226&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@squidfunk&lt;/code&gt; 回复：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/squidfunk-240229.png&quot; alt=&quot;squidfunk-240229&quot;&gt;&lt;/p&gt;
&lt;p&gt;随后，双方公开长篇追溯过往恩怨：&lt;code&gt;@oprypin&lt;/code&gt; &lt;a href=&quot;https://github.com/mkdocs/mkdocs/issues/3579#issuecomment-1970586568&quot; title=&quot;坚称&quot;&gt;坚称&lt;/a&gt; 自己并无过错，&lt;code&gt;@squidfunk&lt;/code&gt; 则&lt;a href=&quot;https://github.com/mkdocs/mkdocs/issues/3579#issuecomment-1970691690&quot; title=&quot;回忆&quot;&gt;回忆&lt;/a&gt; 曾作为 &lt;code&gt;@oprypin&lt;/code&gt; 的首位赞助者，力排众议推荐其加入 MkDocs 团队，但因多次个人冲突，最终选择将沟通限制在公开渠道。&lt;/p&gt;
&lt;p&gt;在完成对文档小问题的修复后，该问题被标记为已解决。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年3月16日---oprypin-将-squidfunk-从组织中移除&quot;&gt;2024年3月16日 - oprypin 将 squidfunk 从组织中移除&lt;/h2&gt;
&lt;p&gt;我们回到&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/2442#discussioncomment-964629&quot; title=&quot;之前的帖子&quot;&gt;之前的帖子&lt;/a&gt;，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 在其中宣布他们已将 &lt;code&gt;@oprypin&lt;/code&gt;、 &lt;code&gt;@squidfunk&lt;/code&gt; 和 &lt;code&gt;@ultrabug&lt;/code&gt; 添加为该项目的维护者。&lt;/p&gt;
&lt;p&gt;3 月 16 日，&lt;code&gt;@squidfunk&lt;/code&gt; 发帖：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/squidfunk-240316.png&quot; alt=&quot;squidfunk-240316&quot;&gt;&lt;/p&gt;
&lt;p&gt;原来，这个决定是 &lt;code&gt;@oprypin&lt;/code&gt; 独自做出的：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-240316.png&quot; alt=&quot;oprypin-240316&quot;&gt;&lt;/p&gt;
&lt;p&gt;不久后，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 加入讨论，恢复了 &lt;code&gt;@squidfunk&lt;/code&gt; 在 MkDocs 组织中的成员身份，并撤销了 &lt;code&gt;@oprypin&lt;/code&gt; 对代码仓库的所有权。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年4月6日---oprypin-下台&quot;&gt;2024年4月6日 - oprypin 下台&lt;/h2&gt;
&lt;p&gt;4 月 6 日，&lt;code&gt;@lovelydinosaur &lt;/code&gt;写道：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-240406.png&quot; alt=&quot;lovelydinosaur-240406&quot;&gt;&lt;/p&gt;
&lt;p&gt;三个小时后，&lt;code&gt;@oprypin&lt;/code&gt; 宣布他将从 MkDocs 项目卸任。在他的帖子中，他详细阐述了他与 &lt;code&gt;@squidfunk&lt;/code&gt; 的分歧、对 &lt;code&gt;@lovelydinosaur&lt;/code&gt; 介入的不满，以及维护该项目的孤独感：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-240406.png&quot; alt=&quot;oprypin-240406&quot;&gt;&lt;/p&gt;
&lt;p&gt;他决定协助团队于 2024 年 4 月 20 日发布 &lt;a href=&quot;https://github.com/mkdocs/mkdocs/releases/tag/1.6.0&quot;&gt;1.6.0&lt;/a&gt; 版本，除同年 8 月 30 日发布的 1.6.1 版本外，截至本文撰写时该版本仍是最新版本。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年4月8日---社区短暂集结&quot;&gt;2024年4月8日 - 社区短暂集结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 卸任两天后，活跃社区成员兼 mkdocstrings 作者 &lt;code&gt;@pawamoy&lt;/code&gt; 在一个题为 &quot;推动 MkDocs 迈向新高度&quot; 的帖子中，邀请约 25 位插件和主题作者共同参与 MkDocs 的维护工作，反响热烈。&lt;code&gt;@squidfunk&lt;/code&gt; 表示对此 &quot;深感振奋（eally psyched to see this happening）&quot;，他还补充道：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/squidfunk-240422.png&quot; alt=&quot;squidfunk-240422&quot;&gt;&lt;/p&gt;
&lt;p&gt;4 月 23 日，在 &lt;code&gt;@oprypin&lt;/code&gt; 发布最后一个版本几天后，&lt;code&gt;@lovelydinosaur&lt;/code&gt; &lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/3677&quot;&gt;发帖宣布&lt;/a&gt; 将回归担任首席维护者。此时，距离其 &lt;a href=&quot;https://github.com/mkdocs/mkdocs/pulls?q=is%3Apr+author%3Alovelydinosaur+is%3Aclosed&quot;&gt;上一次向代码仓库提交的拉取请求被合并&lt;/a&gt; 已过去整整八年。其明确目标为：MkDocs 应拥有 &quot;独立于Material for MkDocs的强有力领导团队&quot;。他们标记了 28 位社区成员，并安排了周六的线上电话会议。&lt;/p&gt;
&lt;p&gt;会议如期举行。&lt;code&gt;@squidfunk&lt;/code&gt; 拒绝参加。仅两人到场。此后十八个月，该帖子再无公开活动记录。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年5月17日---lovelydinosaur-回归&quot;&gt;2024年5月17日 - lovelydinosaur 回归&lt;/h2&gt;
&lt;p&gt;由于 &lt;code&gt;@oprypin&lt;/code&gt; 不再参与该项目，MkDocs 的开发基本上&lt;a href=&quot;https://github.com/ProperDocs/properdocs/graphs/contributors&quot; title=&quot;停止&quot;&gt;停止&lt;/a&gt;了。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/commits2024.png&quot; alt=&quot;commits2024&quot;&gt;&lt;/p&gt;
&lt;p&gt;在向 MkDocs 提交部分拉取请求的同时，他们还开始在 &lt;a href=&quot;https://github.com/mkdocs/sketch/commits/main/&quot; title=&quot;mkdocs/sketch&quot;&gt;mkdocs/sketch&lt;/a&gt; 代码库中着手对 MkDocs 进行重新设计。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年8月1日---无需插件的重新设计&quot;&gt;2024年8月1日 - 无需插件的重新设计&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 向 MkDocs 核心团队展示了其提出的 MkDocs 重新设计方案演示。MkDocs 目前会一次性构建整个站点，而新方案则按需动态生成页面。此提议引发了维护者及核心贡献者的担忧：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/alexvoss-240801.png&quot; alt=&quot;alexvoss-240801&quot;&gt;&lt;/p&gt;
&lt;p&gt;在另一个无关的讨论帖中，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 表示其正致力于对 MkDocs 进行无需任何插件的重新设计。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-240820.png&quot; alt=&quot;lovelydinosaur-240820&quot;&gt;&lt;/p&gt;
&lt;p&gt;该帖子收到的反对票表明，这并非社区真正希望看到的。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2024年8月30日---mkdocs-的最新版本&quot;&gt;2024年8月30日 - MkDocs 的最新版本&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mkdocs/mkdocs/releases/tag/1.6.1&quot; title=&quot;MkDocs 1.6.1&quot;&gt;MkDocs 1.6.1&lt;/a&gt; 版本已发布。截至目前，这仍是最新版本，除对文档进行一两次修订及更新持续集成（CI）徽章外，该代码库没有进行任何积极的开发。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2025年7月17日---这个项目被搁置了吗&quot;&gt;2025年7月17日 - 这个项目被搁置了吗？&lt;/h2&gt;
&lt;p&gt;插件作者 &lt;code&gt;@facelessuser&lt;/code&gt; 发起了一个直接提问的讨论：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/facelessuser-250717.png&quot; alt=&quot;facelessuser-250717&quot;&gt;&lt;/p&gt;
&lt;p&gt;他指出，尽管有积极的赞助商，但修复 bug 的 PR 已经几个月没有经过审核了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@pawamoy&lt;/code&gt; 的回应清晰呈现了自 2024 年 4 月以来发生的情况。他表示曾尝试围绕 &lt;code&gt;@lovelydinosaur&lt;/code&gt; 的回归构建协作维护模式，却发现 &lt;code&gt;@lovelydinosaur&lt;/code&gt; 对现有代码库兴趣寥寥，转而专注于重新设计。当 &lt;code&gt;@pawamoy&lt;/code&gt; 致力于整理待办事项列表并尝试维护项目的同时，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 在被提及 PR 时却&lt;a href=&quot;https://github.com/mkdocs/mkdocs/pull/3913#issuecomment-2717728307&quot;&gt;杳无音信&lt;/a&gt;。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/pawamoy-250723.png&quot; alt=&quot;pawamoy-250723&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 回复称，其一直专注于当前处于早期预发布阶段的 httpx 1.0 项目，并正在考虑是否有时间为 MkDocs 腾出时间。&lt;code&gt;@facelessuser&lt;/code&gt; 回应如下：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/facelessuser-250724.png&quot; alt=&quot;facelessuser-250724&quot;&gt;&lt;/p&gt;
&lt;p&gt;8 月 3 日，大约十天后，&lt;code&gt;@oprypin&lt;/code&gt; 自离开后首次重新出现在该帖子中。他提出回归的条件只有一个：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-250803.png&quot; alt=&quot;oprypin-250803&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 没有回复。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2025年10月15日---幕后工作仍在继续&quot;&gt;2025年10月15日 - 幕后工作仍在继续&lt;/h2&gt;
&lt;p&gt;当 MkDocs 用户纷纷要求修复漏洞时，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 却在另一个尚未公开的代码库（&lt;a href=&quot;https://github.com/encode/mkdocs&quot; title=&quot;encode/mkdocs&quot;&gt;encode/mkdocs&lt;/a&gt;）中继续推进 MkDocs 的重新设计工作。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2025年11月11日---squidfunk-宣布推出-zensical&quot;&gt;2025年11月11日 - squidfunk 宣布推出 Zensical&lt;/h2&gt;
&lt;p&gt;2025 年 11 月 11 日，&lt;a href=&quot;https://github.com/squidfunk/mkdocs-material/releases/tag/9.7.0&quot;&gt;mkdocs-material 9.7.0&lt;/a&gt; 版本发布，同时宣布 Material for MkDocs 项目进入维护模式。与之同步&lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/blog/2025/11/05/zensical/&quot;&gt;发布&lt;/a&gt;的还有 Zensical —— 一款由 Material for MkDocs 团队全新打造的现代化静态网站生成器。团队表示，该工具可原生解析现有 &lt;code&gt;mkdocs.yml&lt;/code&gt; 配置文件，重建速度提升5倍，并配备全新设计的搜索引擎。&lt;/p&gt;
&lt;p&gt;在 2024 年 4 月的维护者讨论帖中（该帖已沉寂数月），有人发布了一篇博文链接，并询问 Material for Mkdocs 进入维护模式是否意味着 MkDocs 需要重新开始。&lt;code&gt;@oprypin&lt;/code&gt; 再次出现，指出 Zensical 就是一个新的开始。关于 MkDocs，他说道：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-251107.png&quot; alt=&quot;oprypin-251107&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 Material for MkDocs 宣布进入维护模式几周后， &lt;code&gt;@jaywhj&lt;/code&gt; 发布了 MaterialX 的&lt;a href=&quot;https://pypi.org/project/mkdocs-materialx/10.0.2/&quot; title=&quot;第一个版本&quot;&gt;第一个版本&lt;/a&gt;，它是 Material for MkDocs 的延续。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2026年1月14日---mkdocs-20&quot;&gt;2026年1月14日 - MkDocs 2.0&lt;/h2&gt;
&lt;p&gt;2026 年 1 月 14 日，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 表示，欢迎他人发布 MkDocs 1.6.2 版本，而其正私下开展 MkDocs v2 的开发工作。&lt;/p&gt;
&lt;p&gt;然后，在 1 月 21 日，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 发起了一个&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/4077&quot;&gt;讨论&lt;/a&gt; ，宣布推出 MkDocs 版本 2：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-260121.png&quot; alt=&quot;lovelydinosaur-260121&quot;&gt;&lt;/p&gt;
&lt;p&gt;绝大多数用户都持负面态度。。社区认为，拟议的 MkDocs 2.0 主要缺点在于缺乏插件支持，而插件正是 MkDocs 最初成功的关键所在。&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/twardoch-260218.png&quot; alt=&quot;twardoch-260218&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@facelessuser&lt;/code&gt; 道出了社区中许多人的心声：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/facelessuser-260203.png&quot; alt=&quot;facelessuser-260203&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 一个月前也&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/3677#discussioncomment-15478473&quot;&gt;表达&lt;/a&gt;了相同观点。社区成员注意到，MkDocs 2.0 版本代码库被放置在 &lt;a href=&quot;https://github.com/encode&quot; title=&quot;encode&quot;&gt;encode&lt;/a&gt; 组织而非 MkDocs 组织下，且 encode 组织的&lt;a href=&quot;https://github.com/encode/.github/blob/master/CONTRIBUTING.md&quot;&gt;贡献指南&lt;/a&gt;明确禁止提交 issue 或 pull request。他们还强调，不公开源代码违背了开源软件开发的原则。最终，&lt;code&gt;@lovelydinosaur&lt;/code&gt; 解释了该代码库设为私有的原因：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-260203.png&quot; alt=&quot;lovelydinosaur-260203&quot;&gt;&lt;/p&gt;
&lt;p&gt;2 月 13 日，&lt;code&gt;@lovelydinosaur&lt;/code&gt; &lt;a href=&quot;https://github.com/encode/mkdocs/commit/313d3e88f7e16103ee0cda36bd8e9d2b68a11533&quot;&gt;删除了&lt;/a&gt;关于计划使 MkDocs 2.x 向后兼容 MkDocs 1.x 的通知。&lt;/p&gt;
&lt;p&gt;2 月 18 日，Material for MkDocs 团队发布了一篇&lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/&quot;&gt;详尽的博客文章&lt;/a&gt;，全面分析了 MkDocs v2 版本为何与其工作及更广泛的插件生态系统不兼容，涵盖从移除插件系统到缺失许可证等诸多问题。&lt;/p&gt;
&lt;h2 id=&quot;user-content-2026年3月9日---pypi-接管事件&quot;&gt;2026年3月9日 - PyPI 接管事件&lt;/h2&gt;
&lt;p&gt;2 月 18 日，Material for MkDocs 在每次 mkdocs 构建时均&lt;a href=&quot;https://github.com/squidfunk/mkdocs-material/releases/tag/9.7.2&quot; title=&quot;显示警告&quot;&gt;显示警告&lt;/a&gt;信息，提醒用户可能存在与 MkDocs 2 未来不兼容的问题，并附上上述博客文章链接。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 此前提出的获取 PyPI 所有权的&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/2442#discussioncomment-2869019&quot; title=&quot;请求&quot;&gt;请求&lt;/a&gt;已于过去某个时间获得批准。虽然他在 2024 年卸任后，其对 GitHub 代码库的权限已被剥夺，但他对 PyPI 仓库的权限仍然有效。&lt;/p&gt;
&lt;p&gt;3 月 9 日，为防止用户项目因意外升级至不受欢迎的 MkDocs 2.0 版本而受损，&lt;code&gt;@oprypin&lt;/code&gt; 采取最后手段，利用其权限将所有其他维护者（包括原作者 &lt;code&gt;@lovelydinosaur&lt;/code&gt;）移出项目。他在 GitHub 的两个讨论区宣布接管：一个在 &lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/4089&quot; title=&quot;mkdocs&quot;&gt;mkdocs&lt;/a&gt;（后被编辑），另一个在 &lt;a href=&quot;https://github.com/orgs/ProperDocs/discussions/1&quot; title=&quot;mkdocs-community&quot;&gt;mkdocs-community&lt;/a&gt;（该组织后更名为 ProperDocs）。其核心理由是，静默发布 v2 版本会导致运行 &lt;code&gt;pip install mkdocs&lt;/code&gt; 的用户意外地破坏他们的项目。他随后在 &lt;code&gt;mkdocs-community/mkdocs&lt;/code&gt; 创建新仓库，宣布未来 PyPI 版本将由此发布，并呼吁社区支持。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 向 &lt;a href=&quot;https://github.com/pypi/support/issues/9671&quot; title=&quot;PyPI 提交了支持工单&quot;&gt;PyPI 提交了支持工单&lt;/a&gt; ，并将 &lt;code&gt;@oprypin&lt;/code&gt; 排除在 MkDocs GitHub 组织之外。&lt;/p&gt;
&lt;p&gt;6 小时内， &lt;code&gt;@oprypin&lt;/code&gt; 做出让步并道歉：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/oprypin-260310.png&quot; alt=&quot;oprypin-260310&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@lovelydinosaur&lt;/code&gt; 重新获得 PyPI 访问权限。&lt;/p&gt;
&lt;p&gt;在另一场&lt;a href=&quot;https://github.com/mkdocs/mkdocs/discussions/4088&quot; title=&quot;讨论&quot;&gt;讨论&lt;/a&gt;中， &lt;code&gt;@Andre601&lt;/code&gt; 表达了他对项目现状的担忧，并就项目维护的未来提出了各种问题， &lt;code&gt;@lovelydinosaur&lt;/code&gt; 对此作出了回应：&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/0001/260301/lovelydinosaur-260309.png&quot; alt=&quot;lovelydinosaur-260309&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;user-content-2026年3月15日---oprypin-推出-properdocs&quot;&gt;2026年3月15日 - oprypin 推出 ProperDocs&lt;/h2&gt;
&lt;p&gt;在接管 PyPI 六天后，&lt;code&gt;@oprypin&lt;/code&gt; 正式发布 &lt;a href=&quot;https://github.com/ProperDocs/properdocs&quot; title=&quot;ProperDocs&quot;&gt;ProperDocs&lt;/a&gt;：一款可无缝替代 MkDocs 1.x 的工具。用户只需将 &lt;code&gt;pip install mkdocs&lt;/code&gt; 替换为 &lt;code&gt;pip install properdocs&lt;/code&gt;，将 &lt;code&gt;mkdocs build&lt;/code&gt; 替换为 &lt;code&gt;properdocs build&lt;/code&gt; 即可。现有插件无需修改代码即可使用。该项目还发布警告，引导用户远离 MkDocs 2.0。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;那么，如今的情况又如何呢？&lt;/p&gt;
&lt;p&gt;原 MkDocs 代码库已有 18 个月未进行实质性开发。其原作者兼唯一维护者 &lt;code&gt;@lovelydinosaur&lt;/code&gt; 正在推进一项缺乏社区支持且会破坏现有插件生态系统的重新设计。&lt;a href=&quot;https://github.com/encode/mkdocs/commits/main/&quot; title=&quot;encode/mkdocs&quot;&gt;encode/mkdocs&lt;/a&gt; v2 代码库自 2026 年 2 月 19 日起已停止更新。&lt;/p&gt;
&lt;p&gt;与此同时，构建该生态系统最广泛使用组件的核心人员已转向其他项目。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@oprypin&lt;/code&gt; 表示，继续推进需要双方达成一致，但认为此举难以成功，因此独立启动了 ProperDocs 项目。该项目并未立即获得巨大成功，一周后，它在 GitHub 上获得了 21 个星标。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@jaywhj&lt;/code&gt; 正在维护 MaterialX，由于原项目 Material for MkDocs 进入了维护模式，该项目似乎在社区中获得了一些关注。&lt;/p&gt;
&lt;p&gt;负责 MkDocs Material 的 &lt;code&gt;@squidfunk&lt;/code&gt; 团队已停止开发，转而从零开始构建 Zensical。MkDocs 生态系统中最活跃的贡献者之一 &lt;code&gt;@pawamoy&lt;/code&gt; 也加入了该项目。截至撰写本文时，Zensical 在 GitHub 上已获得超过 3700 个 star，目前看来是最受欢迎的项目，因此它也极有可能成功，并在未来取代 MkDocs。&lt;/p&gt;
&lt;p&gt;MkDocs 生态系统正在实时分裂。三个继任者，三种愿景，以及一个需要决定押注哪一方的社区。&lt;/p&gt;</content:encoded><category>MkDocs</category></item><item><title>用 Github Discussions 搭建自己的论坛、博客、个人笔记</title><link>https://shenweiyan.github.io/posts/2/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/2/</guid><description>分享一下我的经验。你也可以用 Github Discussions 搭建自己的论坛、博客、个人笔记等。 用户有自主选择权，只看他感兴趣的内容。 可以点击项目的 Watch 按钮来订阅消息。具体用法请看 Github - 查看订阅。 也可以只订阅某个讨论。进入某个讨论，点击右边 Notifications 的 Subscribe 按钮。 用户可以在 里看到订阅的新消息。 这里所有的 Discussi…</description><pubDate>Mon, 26 Oct 2026 06:02:55 GMT</pubDate><content:encoded>&lt;p&gt;分享一下我的经验。你也可以用 Github Discussions 搭建自己的论坛、博客、个人笔记等。&lt;/p&gt;
&lt;h2 id=&quot;user-content-欢迎订阅&quot;&gt;欢迎订阅&lt;/h2&gt;
&lt;p&gt;用户有自主选择权，只看他感兴趣的内容。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可以点击项目的 Watch 按钮来订阅消息。具体用法请看 &lt;a href=&quot;https://docs.github.com/zh/account-and-profile/managing-subscriptions-and-notifications-on-github/managing-subscriptions-for-activity-on-github/viewing-your-subscriptions&quot;&gt;Github - 查看订阅&lt;/a&gt;。&lt;br&gt;
&lt;img alt=&quot;watch-discussions&quot; width=&quot;&quot; src=&quot;https://gi.weiyan.tech/2023/12/watch-github-discussions.png&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;也可以只订阅某个讨论。进入某个讨论，点击右边 Notifications 的 Subscribe 按钮。&lt;br&gt;
用户可以在 &lt;a href=&quot;https://github.com/notifications&quot;&gt;https://github.com/notifications&lt;/a&gt; 里看到订阅的新消息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;user-content-本仓库谈论说明&quot;&gt;本仓库谈论说明&lt;/h2&gt;
&lt;p&gt;这里所有的 &lt;code&gt;Discussion Format&lt;/code&gt; 都是 &lt;code&gt;Announcement&lt;/code&gt;：Only maintainers and admins can post new discussions in these categories, but anyone can comment and reply.&lt;br&gt;
&lt;img src=&quot;https://gi.weiyan.tech/2023/12/discussion-format.png&quot; alt=&quot;Discussion Format&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;user-content-使用讨论-discussion&quot;&gt;使用讨论 (Discussion)&lt;/h2&gt;
&lt;h3 id=&quot;user-content-搜索讨论&quot;&gt;搜索讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;版块搜索：在 Discussions 页面的搜索框输入搜索词即可。&lt;/li&gt;
&lt;li&gt;全局搜索：在最上面的 Github 搜索框输入 &lt;code&gt;org:shenweiyan 搜索词&lt;/code&gt; 就能搜索所有版块的讨论。比如搜&lt;a href=&quot;https://github.com/search?q=org%3Ashenweiyan+%E7%94%9F%E4%BF%A1&amp;#x26;type=discussions&quot;&gt;&quot;生信&quot;&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;user-content-排序讨论&quot;&gt;排序讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;可以按时间来排序讨论。&lt;br&gt;
&lt;img alt=&quot;sort-discussions&quot; width=&quot;280&quot; src=&quot;https://gi.weiyan.tech/2023/12/sort-discussions.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;user-content-使用-label-筛选讨论&quot;&gt;使用 Label 筛选讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Label 是可以无限加的。一个讨论可以有多个 Label。&lt;br&gt;
&lt;img alt=&quot;filter-labels&quot; width=&quot;250&quot; src=&quot;https://gi.weiyan.tech/2023/12/filter-labels.png&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;标记 Label 的好处在于，用户可以在讨论列表里筛选、多选、反选 Label 对应的讨论。&lt;br&gt;
&lt;img alt=&quot;filter-by-labels&quot; width=&quot;250&quot; src=&quot;https://gi.weiyan.tech/2023/12/filter-by-labels.png&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;user-content-评论&quot;&gt;评论&lt;/h2&gt;
&lt;p&gt;评论有两种。一种是在别人的评论下继续回复评论。一种是最底下的评论框，这会回复到主线里。&lt;/p&gt;
&lt;p&gt;如果你针对某个评论进行回复，建议在别人的评论下回复，不要回复到主线。这样其他订阅这个讨论的人不会收到消息提醒的干扰。&lt;/p&gt;
&lt;h2 id=&quot;user-content-讨论管理&quot;&gt;讨论管理&lt;/h2&gt;
&lt;h3 id=&quot;user-content-讨论分类&quot;&gt;讨论分类&lt;/h3&gt;
&lt;p&gt;讨论分类由项目管理员维护。按需创建。分类根据名称排序。所以加个数字前缀用来控制显示顺序。&lt;/p&gt;
&lt;h3 id=&quot;user-content-删除讨论&quot;&gt;删除讨论&lt;/h3&gt;
&lt;p&gt;点进讨论详情页，在右侧最底下有个 delete discussion。&lt;/p&gt;
&lt;h3 id=&quot;user-content-迁移讨论&quot;&gt;迁移讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Transfer this discussion 功能可以把讨论移到同组织的另一个项目下。&lt;br&gt;
&lt;img alt=&quot;transfer-discussion&quot; width=&quot;&quot; src=&quot;https://gi.weiyan.tech/2023/12/transfer-discussion.png&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;也可以切换讨论分类。&lt;br&gt;
&lt;img alt=&quot;change-category&quot; width=&quot;250&quot; src=&quot;https://gi.weiyan.tech/2023/12/change-category.png&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;user-content-置顶讨论&quot;&gt;置顶讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在讨论详情页面可以选择置到项目 Discussion 的顶部，或者置到讨论分类的顶部。&lt;br&gt;
&lt;img alt=&quot;置顶讨论&quot; width=&quot;200&quot; src=&quot;https://gi.weiyan.tech/2023/12/pin-discussion.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;user-content-周报月报&quot;&gt;周报月报&lt;/h2&gt;
&lt;p&gt;可以考虑使用 Github Action 自动跑脚本，通过 Github API 来获取 Discussion 的消息。然后自动创建 release，把简报内容写到 release body 里。这样订阅了项目 release 的人就可以收到。 可以单独创建一个项目来管理，避免订阅的人受到干扰。&lt;/p&gt;
&lt;p&gt;Github Discussion API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/search-github/searching-on-github/searching-discussions&quot;&gt;https://docs.github.com/en/search-github/searching-on-github/searching-discussions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions#search&quot;&gt;https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions#search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/orgs/community/discussions/43&quot;&gt;Discussions API community/community#43&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;user-content-数据备份&quot;&gt;数据备份&lt;/h2&gt;
&lt;p&gt;可以考虑使用 Github Action 自动跑脚本，通过 Github API 来获取 Discussion 的消息。&lt;/p&gt;
&lt;h2 id=&quot;user-content-移动端&quot;&gt;移动端&lt;/h2&gt;
&lt;p&gt;Github 有&lt;a href=&quot;https://github.com/mobile&quot;&gt;移动端 App&lt;/a&gt;。除了不支持 Polls 功能，其他功能都挺好用的。能查看提醒，也能发布新讨论，也能回复评论。&lt;/p&gt;
&lt;h2 id=&quot;user-content-邮件端&quot;&gt;邮件端&lt;/h2&gt;
&lt;p&gt;消息提醒同时会发到你绑定的邮箱里。你也可以用邮箱来查收。&lt;/p&gt;
&lt;h2 id=&quot;user-content-博文中加评论&quot;&gt;博文中加评论&lt;/h2&gt;
&lt;p&gt;借助 &lt;a href=&quot;https://giscus.app/zh-CN&quot;&gt;giscus&lt;/a&gt;，可以非常方便在文章页中插入指定的 discussions —— 在导出 discussions 的时候，在文章尾部增加类似以下 JavaScript 即可：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;&amp;#x3C;script src=&quot;https://giscus.app/client.js&quot;
        data-repo=&quot;shenweiyan/shenweiyan.github.io&quot;
        data-repo-id=&quot;R_kgDOKgxWlg&quot;
        data-mapping=&quot;number&quot;
        data-term=&quot;4&quot;
        data-reactions-enabled=&quot;1&quot;
        data-emit-metadata=&quot;0&quot;
        data-input-position=&quot;bottom&quot;
        data-theme=&quot;light&quot;
        data-lang=&quot;zh-CN&quot;
        crossorigin=&quot;anonymous&quot;
        async&gt;
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>Discussions</category></item><item><title>Welcome to repozine-demo Discussions!</title><link>https://shenweiyan.github.io/posts/1/</link><guid isPermaLink="true">https://shenweiyan.github.io/posts/1/</guid><description>We’re using Discussions as a place to connect with other members of our community. We hope that you: Ask questions you’re wondering about. Share ideas. Engage with other community members. Welcome oth…</description><pubDate>Mon, 26 Oct 2026 05:17:34 GMT</pubDate><content:encoded>
&lt;h2 id=&quot;user-content--welcome&quot;&gt;👋 Welcome!&lt;/h2&gt;
&lt;p&gt;We’re using Discussions as a place to connect with other members of our community. We hope that you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ask questions you’re wondering about.&lt;/li&gt;
&lt;li&gt;Share ideas.&lt;/li&gt;
&lt;li&gt;Engage with other community members.&lt;/li&gt;
&lt;li&gt;Welcome others and are open-minded. Remember that this is a community we&lt;br&gt;
build together 💪.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get started, comment below with an introduction of yourself and tell us about what you do with this community.&lt;/p&gt;
</content:encoded><category>Announcements</category></item></channel></rss>