目录
- 引子
- rich
-
- 快速开始
- 终端输出控制台
- 样式
- 控制台标记
- 富文本
- 高亮
- 美化打印
-
- [pprint 方法(pprint method)](#pprint 方法(pprint method))
-
- [缩进指引线(Indent guides)](#缩进指引线(Indent guides))
- [全部展开(Expand all)](#全部展开(Expand all))
- [截断美化输出(Truncating pretty output)](#截断美化输出(Truncating pretty output))
- [可渲染的 Pretty](#可渲染的 Pretty)
- [Rich Repr 协议(Rich Repr Protocol)](#Rich Repr 协议(Rich Repr Protocol))
-
- 类型支持(Typing)
- [自动 Rich Repr(Automatic Rich Repr)](#自动 Rich Repr(Automatic Rich Repr))
- [日志处理器(Logging Handler)](#日志处理器(Logging Handler))
-
- [异常处理(Handle exceptions)](#异常处理(Handle exceptions))
- [抑制框架栈(Suppressing Frames)](#抑制框架栈(Suppressing Frames))
- 异常回溯(Traceback)
-
- [打印异常(Printing tracebacks)](#打印异常(Printing tracebacks))
- [Traceback Handler(全局异常处理器)](#Traceback Handler(全局异常处理器))
- [自动安装 Traceback(Automatic Traceback Handler)](#自动安装 Traceback(Automatic Traceback Handler))
- [抑制框架栈(Suppressing Frames)](#抑制框架栈(Suppressing Frames))
- [最大帧数(Max Frames)](#最大帧数(Max Frames))
- 提示输入(Prompt)
- 列布局(Columns)
- [渲染组(Render Groups)](#渲染组(Render Groups))
- [Markdown 渲染(Markdown)](#Markdown 渲染(Markdown))
- 内边距(Padding)
- 面板(Panel)
- [进度显示(Progress Display)](#进度显示(Progress Display))
-
- 基本用法
- 高级用法
-
- 启动与停止
- 更新任务
- 隐藏任务
- 临时进度(Transient)
- 不确定进度(Indeterminate)
- [自动刷新(Auto refresh)](#自动刷新(Auto refresh))
- 展开模式(Expand)
- 列(Columns)
- [自定义表格列宽(Table Columns)](#自定义表格列宽(Table Columns))
- [打印 / 日志输出(Print / Log)](#打印 / 日志输出(Print / Log))
- [stdout / stderr 重定向](#stdout / stderr 重定向)
- 自定义进度显示(Customization)
- 文件读取进度
- [嵌套进度条(Nesting Progress Bars)](#嵌套进度条(Nesting Progress Bars))
- [多 Progress 实例(Multiple Progress)](#多 Progress 实例(Multiple Progress))
- 语法高亮(Syntax)
- 表格(Tables)
-
- [表格特性(Table Options)](#表格特性(Table Options))
- [边框样式(Border Styles)](#边框样式(Border Styles))
- 行线(Lines)
- [空表处理(Empty Tables)](#空表处理(Empty Tables))
- [添加列(Adding Columns)](#添加列(Adding Columns))
- [列选项(Column Options)](#列选项(Column Options))
- [垂直对齐(Vertical Alignment)](#垂直对齐(Vertical Alignment))
- 网格布局(Grids)
- 树形结构(Tree)
-
- [树样式(Tree Styles)](#树样式(Tree Styles))
- [Live 显示(Live Display)](#Live 显示(Live Display))
-
- [基本用法(Basic Usage)](#基本用法(Basic Usage))
- [动态更新 renderable(Updating Renderable)](#动态更新 renderable(Updating Renderable))
- [全屏模式(Alternate Screen)](#全屏模式(Alternate Screen))
- [临时显示(Transient Display)](#临时显示(Transient Display))
- [自动刷新(Auto Refresh)](#自动刷新(Auto Refresh))
- [垂直溢出(Vertical Overflow)](#垂直溢出(Vertical Overflow))
- [print / log 输出](#print / log 输出)
- [stdout / stderr 重定向](#stdout / stderr 重定向)
- [嵌套 Live(Nesting)](#嵌套 Live(Nesting))
- 布局(Layout)
-
- [创建布局(Creating Layouts)](#创建布局(Creating Layouts))
- 布局(Layout)
-
- 创建布局
- 设置渲染内容(Renderables)
- [固定大小(Fixed size)](#固定大小(Fixed size))
- 比例布局(Ratio)
- 可见性(Visibility)
- 结构树(Tree)
- 控制台协议
-
- 控制台自定义
- 控制台渲染(高级)
- 底层渲染(Segment)
- [可视化测量(Measuring Renderables)](#可视化测量(Measuring Renderables))
- 附录
- prompt_toolkit
-
- 快速开始
- Printing(以及使用)格式化文本
-
- [Printing plain text(打印普通文本)](#Printing plain text(打印普通文本))
- [Formatted text(格式化文本)](#Formatted text(格式化文本))
-
- HTML
- ANSI
- [(style, text) 元组](#(style, text) 元组)
- [Pygments (Token, text) 元组](#Pygments (Token, text) 元组)
- to_formatted_text
- [Asking for input (prompts)(输入提示 / prompts)](#Asking for input (prompts)(输入提示 / prompts))
-
- [Hello world](#Hello world)
-
- [The PromptSession object(PromptSession 对象)](#The PromptSession object(PromptSession 对象))
- [Syntax highlighting(语法高亮)](#Syntax highlighting(语法高亮))
- Colors(颜色)
-
- [Using a Pygments style(使用 Pygments 样式)](#Using a Pygments style(使用 Pygments 样式))
- 自动补全(Autocompletion)
-
- [嵌套补全(Nested completion)](#嵌套补全(Nested completion))
- [自定义补全器(A custom completer)](#自定义补全器(A custom completer))
- [单个补全项的样式(Styling individual completions)](#单个补全项的样式(Styling individual completions))
- [模糊补全(Fuzzy completion)](#模糊补全(Fuzzy completion))
- [异步补全(Asynchronous completion)](#异步补全(Asynchronous completion))
- [输入校验(Input validation)](#输入校验(Input validation))
-
- [使用函数创建验证器(Validator from a callable)](#使用函数创建验证器(Validator from a callable))
- 历史记录(History)
- [自动建议(Auto suggestion)](#自动建议(Auto suggestion))
- [添加底部工具栏(Adding a bottom toolbar)](#添加底部工具栏(Adding a bottom toolbar))
- [添加右侧提示(Adding a right prompt)](#添加右侧提示(Adding a right prompt))
- [Vi 输入模式(Vi input mode)](#Vi 输入模式(Vi input mode))
- [添加自定义按键绑定(Adding custom key bindings)](#添加自定义按键绑定(Adding custom key bindings))
-
- [根据条件启用按键绑定(Enable key bindings according to a condition)](#根据条件启用按键绑定(Enable key bindings according to a condition))
- [在 Emacs 和 Vi 模式之间动态切换(Dynamically switch between Emacs and Vi mode)](#在 Emacs 和 Vi 模式之间动态切换(Dynamically switch between Emacs and Vi mode))
- [使用 control-space 触发补全(Using control-space for completion)](#使用 control-space 触发补全(Using control-space for completion))
- [其他 prompt 选项](#其他 prompt 选项)
-
- [多行输入(Multiline input)](#多行输入(Multiline input))
- [设置默认值(Passing a default)](#设置默认值(Passing a default))
- [鼠标支持(Mouse support)](#鼠标支持(Mouse support))
- [换行方式(Line wrapping)](#换行方式(Line wrapping))
- [密码输入(Password input)](#密码输入(Password input))
- [光标形状(Cursor shapes)](#光标形状(Cursor shapes))
- [添加边框(Adding a frame)](#添加边框(Adding a frame))
- [在 asyncio 应用中使用 prompt(Prompt in an asyncio application)](#在 asyncio 应用中使用 prompt(Prompt in an asyncio application))
- [不显示 prompt,逐键读取 stdin(Reading keys from stdin, one key at a time, but without a prompt)](#不显示 prompt,逐键读取 stdin(Reading keys from stdin, one key at a time, but without a prompt))
- [询问选择(Asking for a choice)](#询问选择(Asking for a choice))
-
- [选项着色(Coloring the options)](#选项着色(Coloring the options))
- [添加边框(Adding a frame)](#添加边框(Adding a frame))
- [添加底部工具栏(Adding a bottom toolbar)](#添加底部工具栏(Adding a bottom toolbar))
- 对话框(Dialogs)
-
- [消息框(Message box)](#消息框(Message box))
- [输入框(Input box)](#输入框(Input box))
- [是/否确认对话框(Yes/No confirmation dialog)](#是/否确认对话框(Yes/No confirmation dialog))
- [按钮对话框(Button dialog)](#按钮对话框(Button dialog))
- [单选列表对话框(Radio list dialog)](#单选列表对话框(Radio list dialog))
- [多选列表对话框(Checkbox list dialog)](#多选列表对话框(Checkbox list dialog))
- [对话框样式(Styling of dialogs)](#对话框样式(Styling of dialogs))
- 结尾

引子
使用Claude Code 这种工具的时候,其实挺容易产生一种落差感。
你会发现,同样是在终端里,它的体验却完全不像"终端程序":
输入是流畅的,有补全、有结构感,不再是生硬的一行行文本;
输出是有层次的,颜色、布局、甚至信息密度都被精心设计过,看起来更像一个界面,而不是简单的 print。
那一刻很容易产生一个想法------
这玩意到底是怎么做出来的?
更具体一点说:
为什么我们自己写的 CLI,永远停留在 input + print 的阶段,
而这些工具,却已经把终端做成了一种"轻量级 UI"?
一开始可能会觉得,这背后是不是一整套很重的前端体系,甚至是某种黑科技。
但真正去拆的时候会发现,答案反而很"朴素":
输入和输出,被拆开各自做到极致。
在 Python 生态里,对应的其实正是两个库:prompt_toolkit 和 Rich。
前者解决的是"你如何与用户交互";
后者解决的是"你如何把信息展示出来"。
它们并不是什么新东西,但当你把这两块拼在一起的时候,就会突然意识到一件事:
原来限制终端体验的,从来不是终端本身,而是我们写代码的方式。
rich
很多人第一次意识到终端也可以"很好看",往往不是因为自己写代码,而是因为用了某个工具。
日志是分层的,错误是高亮的,表格是规整的,甚至连一段普通的文本,都有清晰的结构和节奏感。那种体验很微妙------你明知道它只是终端输出,但看起来却更像一个被设计过的界面。
反过来看我们自己写的程序,大多数时候却停留在另一种状态:
print 到处都是,信息堆在一起,没有层次,也没有重点。代码逻辑也许没问题,但一旦输出复杂起来,可读性就迅速崩掉。
问题其实很简单:
终端并不缺能力,缺的是"渲染"。
在 Python 生态里,Rich 做的正是这件事------
它不是简单地给 print 加点颜色,而是把"终端输出"这件事,当成一个可以被设计的系统来对待。
快速开始
Rich 是一个用于在终端输出富文本(包含颜色和样式)的 Python 库,同时还支持展示更复杂的内容,例如表格、Markdown,以及带语法高亮的代码。
你可以使用 Rich 来让命令行程序更具视觉表现力,并以更易读的方式展示数据。此外,Rich 还可以作为调试工具,通过美化输出和语法高亮,让数据结构更加清晰直观。
Rich 支持以下操作系统:
- macOS
- Linux
- Windows
在 Windows 上,既支持传统的 cmd.exe 终端,也支持新的 Windows Terminal(后者对颜色和样式的支持更好)。
Rich 需要 Python 3.8 及以上版本。
注意
如果你使用的是 PyCharm,需要在运行/调试配置中开启 "emulate terminal",否则无法看到样式效果。
或者可以直接使用git bash或者cmdline去查看样式:

我们可以通过 pip 安装:
bash
pip install rich
如果已经安装,可以使用 -U 参数升级到最新版本:
bash
pip install -U rich
如果你打算在 Jupyter 中使用 Rich,需要额外安装依赖:
bash
pip install "rich[jupyter]"
你可以通过以下命令快速验证是否安装成功,并查看 Rich 的效果:
bash
python -m rich
最直接的入门方式,是直接使用 Rich 提供的 print 函数,它的参数和 Python 内置的 print 完全一致,可以作为替代品使用:
python
from rich import print
之后你就可以像平常一样输出内容,Rich 会自动进行基础的语法高亮,并对数据结构进行格式化,使其更易读。
字符串中还可以使用 Console Markup 来添加颜色和样式。
例如:
python
print("[italic red]Hello[/italic red] World!", locals())
输出效果(包含颜色和样式)类似如下:
Hello World!
{
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__doc__': None,
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__name__': '__main__',
'__package__': None,
'__spec__': None,
'print': <function print at 0x1027fd4c0>,
}
如果你不想覆盖内置的 print,也可以这样导入:
python
from rich import print as rprint
Rich 可以直接集成到 Python REPL 中,使输出自动具备高亮和美化效果:
python
from rich import pretty
pretty.install()
["Rich and pretty", True]
此外,你还可以用它来测试 Rich 的渲染组件(renderables):
python
from rich.panel import Panel
Panel.fit("[bold yellow]Hi, I'm a Panel", border_style="red")
Rich 还提供了 IPython 扩展,可以实现自动美化输出和更友好的异常信息:
python
%load_ext rich
你也可以在 IPython 配置中,将 "rich" 添加到扩展列表中,实现自动加载。
(c.InteractiveShellApp.extension)
Rich 提供了一个非常实用的调试工具:inspect(),可以生成任意 Python 对象的详细报告。
示例:
python
from rich import inspect
from rich.color import Color
color = Color.parse("red")
inspect(color, methods=True)
这个功能在调试时非常有用,也很好地展示了 Rich 的输出能力。
终端输出控制台
如果你希望对终端输出拥有更完整的控制能力,Rich 提供了一个核心类:Console。
在大多数应用中,通常只需要一个全局的 Console 实例。因此,一个常见的做法是在项目中创建一个统一的入口,比如新建一个 console.py:
python
from rich.console import Console
console = Console()
之后你可以在项目的任意位置直接导入使用:
python
from my_project.console import console
Console 本质上负责一件事情:
👉 把你写的内容,转换成终端能够理解的格式(ANSI 转义序列),从而实现颜色、样式等效果。
同时,它还会自动检测当前终端的能力,比如:
- 是否支持颜色
- 支持多少种颜色
- 是否需要做兼容转换
换句话说,你只需要"描述你想要的效果",剩下的交给 Console。
而快速开始提到的print方法实际上也是基于默认的Console进行输出的:

属性
Console 会自动检测一些与渲染相关的重要属性:
-
size
当前终端的尺寸(窗口大小改变时会动态变化)
-
encoding
默认编码(通常是
"utf-8") -
is_terminal
是否正在输出到真正的终端(布尔值)
-
color_system
当前终端支持的颜色系统(下面会详细说明)
颜色系统
终端的颜色并不是统一标准的,不同环境支持的能力差别很大。
Rich 会自动检测当前终端支持的颜色系统,但你也可以在创建 Console 时手动指定:
python
Console(color_system="256")
支持的取值如下:
-
None
完全禁用颜色
-
"auto"
自动检测(默认)
-
"standard"
支持 8 种基础颜色(加亮色共 16 种)
-
"256"
支持 256 色(在 standard 基础上增加 240 种固定颜色)
-
"truecolor"
支持约 1670 万种颜色(基本等同于显示器能力)
-
"windows"
旧版 Windows 终端(仅支持 8 色)
(新 Windows Terminal 实际支持 truecolor)
⚠️ 注意
手动设置颜色系统时需要谨慎:
如果你设置的颜色能力 高于终端实际支持的能力,输出可能会出现异常,甚至变得不可读。
输出(打印)
要向终端输出富文本内容,可以使用 console.print() 方法。
Rich 会自动将任意对象通过其 __str__ 方法转换为字符串,并进行基础的语法高亮。同时,对于容器类型(如 dict、list),还会进行美化排版(pretty print)。
如果输出的是字符串,还会解析其中的 Console Markup(用于添加颜色和样式)。
例如:
python
console.print([1, 2, 3])
console.print("[blue underline]Looks like a link")
console.print(locals())
console.print("FOO", style="white on blue")
此外,print() 还可以直接渲染支持 Console Protocol 的对象。这包括 Rich 内置的组件,例如 Text、Table、Syntax,以及你自定义的对象。
日志输出
console.log() 方法在 print() 的基础上增加了一些对调试非常有用的功能。
它会在输出中自动附加:
- 左侧:当前时间
- 右侧:调用该方法的文件名和行号
示例:
python
console.log("Hello, World!")
输出类似:
id="k4gq2k"
[16:32:08] Hello, World! <stdin>:1
此外,log() 还支持一个很实用的参数:log_locals=True。
开启后,Rich 会自动打印当前作用域中的局部变量(以表格形式展示),对调试非常友好。
python
from rich.console import Console
console = Console()
a= 10
if __name__ == '__main__':
console.log("hello",log_locals=True)

JSON 输出
console.print_json() 方法可以对 JSON 字符串进行美化输出(格式化 + 语法高亮)。
例如:
python
console.print_json('[false, true, null, "foo"]')
你也可以通过 log() 输出 JSON 对象:
python
from rich.json import JSON
console.log(JSON('["foo", "bar"]'))
由于这是一个常见需求,Rich 也提供了更简便的导入方式:
python
from rich import print_json
此外,还可以直接在命令行中使用:
bash
python -m rich.json cats.json
底层输出
除了 print() 和 log(),Rich 还提供了一个更底层的方法:console.out()。
这个方法的特点是:
- 只会将参数转换为字符串
- 不会进行美化排版(pretty print)
- 不会自动换行
- 不会解析 markup
但仍然可以:
- 应用基础样式
- 可选开启高亮
示例:
python
console.out("Locals", locals())
👉 print / log / out 本质上是 三个不同抽象层级的输出接口
print:面向展示(默认最智能)log:面向调试(附带上下文)out:面向控制(最接近底层)
分割线
console.rule() 方法可以绘制一条横线,并可选地带一个标题,非常适合用来对终端输出进行分段。
例如:
python
console.rule("[bold red]Chapter 2")
效果类似:
id="v4d8tm"
─────────────────────────────── Chapter 2 ───────────────────────────────
此外,rule() 还支持:
style:设置横线样式align:控制标题对齐方式("left"、"center"、"right")
状态指示(加载动画)
Rich 可以显示带有"加载动画(spinner)"的状态信息,并且不会影响正常的终端输出。
你可以运行以下命令查看效果:
bash
python -m rich.status
在代码中,可以通过 console.status() 来使用这个功能。它返回的是一个上下文管理器,会在代码块执行期间显示状态,执行结束后自动关闭。
示例:
python
with console.status("Working..."):
do_work()
你还可以通过 spinner 参数自定义动画样式:
python
with console.status("Monkeying around...", spinner="monkey"):
do_work()
可用的 spinner 列表可以通过以下命令查看:
bash
python -m rich.spinner
对齐方式
console.print() 和 console.log() 都支持一个 justify 参数,用于控制文本对齐方式,可选值包括:
"default"(默认)"left""right""center""full"
它们的含义如下:
- left:左对齐
- right:右对齐
- center:居中
- full:两端对齐(类似书籍排版)
默认的 "default" 看起来和 "left" 很像,但实际上有一个细微差别:
"left":会用空格填充右侧"default":不会填充
这个差异通常只有在设置了背景色时才明显。
示例:
python
from rich.console import Console
console = Console(width=20)
style = "bold white on blue"
console.print("Rich", style=style)
console.print("Rich", style=style, justify="left")
console.print("Rich", style=style, justify="center")
console.print("Rich", style=style, justify="right")
输出效果类似:
id="c7h4vx"
Rich
Rich
Rich
Rich
👉 Rich 已经不只是"打印文本",而是在做终端排版系统
rule→ 分割结构status→ 动态状态justify→ 排版控制
这已经开始有点像"终端里的 UI layout"了,而不是简单输出。
溢出处理
所谓 Overflow(溢出),指的是你输出的文本长度超过了当前可用空间。
这种情况通常会出现在:
- 很长的单词(例如 URL)
- 表格单元格空间不足
- Panel 内部宽度受限
- 终端窗口较窄
你可以通过 print() 的 overflow 参数来控制 Rich 如何处理这些超出的内容。
可选值包括:
"fold""crop""ellipsis""ignore"
默认值是:
python
overflow="fold"
fold(默认)
把超出的文本折叠到下一行,直到全部显示完。
python
console.print(text, overflow="fold")
效果:
id="m2v5qt"
supercalifragi
listicexpialid
ocious
crop
直接裁剪超出的部分,不再显示。
python
console.print(text, overflow="crop")
效果:
id="k6n1ew"
supercalifragi
ellipsis
裁剪超出的部分,并在结尾加上省略号。
python
console.print(text, overflow="ellipsis")
效果:
id="g3j8hf"
supercalifrag...
ignore
忽略宽度限制,让文本继续延伸。
python
console.print(text, overflow="ignore")
通常看起来和 crop 类似,除非同时设置:
python
crop=False
控制台样式
Console 对象有一个 style 属性,可以为所有输出统一设置默认样式。
默认值是:
python
style=None
例如:
python
from rich.console import Console
blue_console = Console(style="white on blue")
blue_console.print("I'm blue.")
这样这个 Console 输出的所有内容都会带上相同的风格。
软换行
默认情况下,Rich 会自动对长文本进行换行,使内容适配终端宽度。
如果你希望关闭这种行为,可以设置:
python
soft_wrap=True
示例:
python
console.print(text, soft_wrap=True)
开启后,文本会像 Python 原生 print() 一样自然延续到下一行。
裁剪
print() 方法还有一个布尔参数:
python
crop=True
默认情况下,Rich 会裁剪那些本来会溢出的内容。
一般来说你很少需要手动控制这个参数,因为 Rich 会尽量自动适配当前终端宽度。
注意
如果设置了:
pythonsoft_wrap=True那么
crop会自动失效。
输入
除了输出之外,Console 还提供了一个 input() 方法。
它和 Python 内置的 input() 用法相同,但提示文字可以使用 Rich 的样式。
例如:
python
from rich.console import Console
console = Console()
console.input("What is [i]your[/i] [bold red]name[/]? :smiley: ")
你可以得到一个带颜色、样式甚至 emoji 的输入提示。
如果系统提前加载了 Python 的 readline 模块,console.input() 还可以支持:
- 行内编辑
- 输入历史
- 更自然的命令行体验
导出
Console 不仅可以输出到终端,还可以将所有输出内容导出为:
- 文本(text)
- SVG
- HTML
要启用导出功能,需要在创建 Console 时开启 record=True:
python
from rich.console import Console
console = Console(record=True)
这个参数的作用是:
👉 让 Rich 记录你所有通过 print() 和 log() 输出的内容。
在输出完成之后,你可以通过以下方法获取结果(字符串形式):
python
console.export_text()
console.export_svg()
console.export_html()
或者直接写入文件:
python
console.save_text("output.txt")
console.save_svg("output.svg")
console.save_html("output.html")
导出 SVG
当使用 export_svg() 或 save_svg() 时:
- SVG 的宽度会与当前终端宽度(字符数)一致
- 高度会根据内容自动扩展
生成的 SVG 可以:
- 直接在浏览器中打开
- 用
<img>标签嵌入网页 - 或直接嵌入 HTML 代码中
你还可以自定义 SVG 的主题(颜色风格):
python
from rich.console import Console
from rich.terminal_theme import MONOKAI
console = Console(record=True)
console.save_svg("example.svg", theme=MONOKAI)
如果默认主题不满足需求,也可以自己构建一个 TerminalTheme。
注意
导出的 SVG 默认使用 Fira Code 字体 。
如果你将 SVG 嵌入网页,建议同时引入该字体的 CSS。

错误控制台
默认情况下,Console 的输出会写入:
python
sys.stdout
也就是标准输出(终端正常显示内容)。
如果你在创建 Console 时设置:
python
Console(stderr=True)
那么输出会写入:
python
sys.stderr
也就是标准错误流。
这在工程中很有用,你可以专门创建一个"错误控制台",用于区分普通输出和错误信息:
python
from rich.console import Console
error_console = Console(stderr=True)
你还可以给错误输出设置统一风格,例如:
python
error_console = Console(stderr=True, style="bold red")
这样错误信息会在视觉上更加明显。
文件输出
你可以通过在创建 Console 时指定 file 参数,让输出直接写入文件,而不是终端。
这个参数需要传入一个可写的类文件对象(file-like object)。
例如:
python
import sys
from rich.console import Console
from datetime import datetime
with open("report.txt", "wt") as report_file:
console = Console(file=report_file)
console.rule(f"Report Generated {datetime.now().ctime()}")
这样所有输出都会写入 report.txt,不会出现在终端。
需要注意的是:
👉 写入文件时,建议显式指定 width 参数
否则 Rich 会按照当前终端宽度进行换行。
输出捕获
有时候你可能不希望直接输出,而是捕获输出结果(例如用于日志处理或测试)。
可以使用 capture() 方法,它返回一个上下文管理器。
在代码块结束后,通过 get() 获取输出内容:
python
from rich.console import Console
console = Console()
with console.capture() as capture:
console.print("[bold red]Hello[/] World")
str_output = capture.get()
另一种更常见的方式(尤其是单元测试中)是使用 io.StringIO:
python
from io import StringIO
from rich.console import Console
console = Console(file=StringIO())
console.print("[bold red]Hello[/] World")
str_output = console.file.getvalue()
👉 这种方式更适合测试场景。
分页显示
当输出内容很长时,可以使用分页器(pager)来展示。
分页器通常支持:
- 按键滚动
- 上下浏览
- 搜索等功能(取决于系统工具)
使用方式如下:
python
from rich.__main__ import make_test_card
from rich.console import Console
console = Console()
with console.pager():
console.print(make_test_card())
当退出 pager 时,内容会被发送到分页器中显示。
需要注意:
👉 大多数默认分页器不支持颜色,因此 Rich 会自动移除样式。
如果你确认分页器支持颜色,可以这样开启:
python
console.pager(styles=True)
补充
Rich 会优先读取环境变量:
MANPAGERPAGER在 Linux / macOS 上,你可以设置:
bashless -r来支持 ANSI 颜色输出。
备用屏幕(全屏模式)
⚠️ 注意:该功能目前仍属于实验性特性
终端通常支持一种"备用屏幕(alternate screen)"模式:
- 类似全屏界面
- 不会影响原有终端内容
- 退出后恢复原状
Rich 通过 screen() 提供了这个能力(推荐用法),它返回一个上下文管理器:
python
from time import sleep
from rich.console import Console
console = Console()
with console.screen():
console.print(locals())
sleep(5)
运行效果:
👉 在一个"临时全屏界面"中显示内容
👉 5 秒后自动返回终端
你还可以动态更新屏幕内容:
python
from time import sleep
from rich.console import Console
from rich.align import Align
from rich.text import Text
from rich.panel import Panel
console = Console()
with console.screen(style="bold white on red") as screen:
for count in range(5, 0, -1):
text = Align.center(
Text.from_markup(f"[blink]Don't Panic![/blink]\n{count}", justify="center"),
vertical="middle",
)
screen.update(Panel(text))
sleep(1)
这种方式可以实现:
- 动态刷新内容
- 自动裁剪屏幕
- 不产生滚动
如果你需要构建更复杂的全屏界面,可以进一步了解:
👉 Live Display(Rich 的动态 UI 系统)
注意
如果程序异常退出导致终端卡在"备用屏幕模式",可以在终端输入:
bashreset来恢复。
终端检测
Rich 会自动检测当前输出是否指向一个"终端"。
如果检测到不是终端(例如输出被重定向到文件),Rich 会自动移除控制字符(如颜色、样式对应的 ANSI 转义序列),只保留纯文本。
如果你希望即使写入文件也保留这些控制字符,可以在创建 Console 时设置:
python
Console(force_terminal=True)
这种自动检测机制其实很实用:
👉 当你把输出通过管道传递给文件或其他程序时,Rich 会自动降级为纯文本输出,避免产生乱码。
交互模式
当输出目标不是终端时,Rich 默认会关闭一些"交互式效果",例如:
- 进度条(progress bar)
- 状态动画(spinner)
因为这些内容通常不适合写入文件。
你可以通过 force_interactive 参数手动控制这一行为:
python
Console(force_interactive=True) # 强制开启动画
Console(force_interactive=False) # 强制关闭动画
环境变量
Rich 支持并遵循一些常见的环境变量,用于控制行为。
TERM
bash
TERM=dumb 或 unknown
会禁用:
- 颜色 / 样式
- 依赖光标控制的功能(如进度条)
FORCE_COLOR
bash
FORCE_COLOR=1
即使 TERM 不支持,也强制开启颜色。
NO_COLOR
bash
NO_COLOR=1
完全禁用颜色输出(优先级高于 FORCE_COLOR)。
注意
NO_COLOR 只会移除"颜色",但不会移除:
- bold
- italic
- underline
等样式
TTY_COMPATIBLE
用于覆盖 Rich 的终端检测逻辑:
1→ 强制认为是终端(支持 ANSI)0→ 强制认为不是终端- 未设置 → 自动检测
TTY_INTERACTIVE
用于控制是否启用"交互模式":
1→ 强制开启0→ 强制关闭- 未设置 → 自动检测
💡 在 CI / GitHub Actions 中的建议
如果你希望在 CI 环境中使用 Rich:
bash
TTY_COMPATIBLE=1
TTY_INTERACTIVE=0
这表示:
- 可以输出 ANSI 样式
- 但没有真实用户交互(因此关闭动画)
COLUMNS / LINES
如果没有在 Console 构造函数中指定:
python
Console(width=..., height=...)
则可以通过环境变量控制终端尺寸:
bash
COLUMNS=120
LINES=40
在 Jupyter 中对应的是:
bash
JUPYTER_COLUMNS
JUPYTER_LINES
优先级说明:
环境变量只是提供默认值:
👉 如果你在 Console 构造函数中显式传参,这些参数会覆盖环境变量。
样式
在 Rich 的各个 API 中,你经常会看到一个 style 参数。
它用于定义文本的显示样式,包括:
- 颜色
- 加粗、斜体等属性
一个样式可以是:
- 字符串(最常见)
- 或
Style类的实例
定义样式
样式本质上是一个字符串,由一个或多个"关键词"组成,用来描述颜色和样式属性。
比如前景色(文字颜色)可以直接使用 256 色标准名称,例如:
python
console.print("Hello", style="magenta")
也可以用颜色编号(0~255):
python
console.print("Hello", style="color(5)")
使用 RGB / HEX(推荐)你还可以用类似 CSS 的方式定义颜色:
python
console.print("Hello", style="#af00ff")
console.print("Hello", style="rgb(175,0,255)")
👉 这种方式支持 truecolor(约 1670 万种颜色)
注意
有些终端只支持 256 色,Rich 会自动选择最接近的颜色。
背景色使用 on 关键字:
python
console.print("DANGER!", style="red on white")
默认颜色可以使用:
python
style="default"
或者:
python
style="default on default"
恢复终端默认颜色。
你还可以在 style 中添加以下关键词:
- bold / b → 加粗
- italic / i → 斜体(Windows 不支持)
- underline / u → 下划线
- strike / s → 删除线
- reverse / r → 前景色和背景色反转
- blink → 闪烁(慎用)
- blink2 → 快速闪烁(大多终端不支持)
- conceal → 隐藏文本(很少支持)
不太通用的样式(可能不生效)
- underline2 / uu → 双下划线
- frame → 边框
- encircle → 圆圈包围
- overline / o → 上划线
颜色和样式可以自由组合:
python
console.print(
"Danger, Will Robinson!",
style="blink bold red underline on white"
)
可以通过 not 前缀关闭某些样式:
python
console.print("foo [not bold]bar[/not bold] baz", style="bold")
效果:
foo→ 加粗bar→ 普通baz→ 加粗
样式还可以包含 link 属性,使文本变成可点击链接(如果终端支持):
python
console.print("Google", style="link https://google.com")
说明
如果你熟悉 HTML,可能会觉得这种写法有点奇怪。
但在终端里,"链接"本质上只是一个样式属性,就像 bold、italic 一样。
👉 Rich 的 style 本质上是一种"终端版 CSS"
- 前景色 / 背景色 → 类似 color / background
- 属性(bold / underline) → 类似 font-weight / text-decoration
- link → 类似
样式类
最终,样式定义会被解析,并生成一个 Style 类的实例。
如果你愿意,也可以直接使用 Style 类来代替样式字符串定义。例如:
python
from rich.style import Style
danger_style = Style(color="red", blink=True, bold=True)
console.print("Danger, Will Robinson!", style=danger_style)
以这种方式构造 Style 类会稍微更快一些,因为样式字符串需要解析------不过这种开销只发生在第一次调用时,因为 Rich 会缓存已解析的样式定义。
样式可以通过相加的方式进行组合,这在你想基于已有样式进行修改时非常有用。例如:
python
from rich.console import Console
from rich.style import Style
console = Console()
base_style = Style.parse("cyan")
console.print("Hello, World", style=base_style + Style(underline=True))
你也可以使用 parse() 方法显式解析样式定义,它会接收一个样式字符串并返回一个 Style 实例。例如,下面两行是等价的:
python
style = Style(color="magenta", bgcolor="yellow", italic=True)
style = Style.parse("italic magenta on yellow")
样式主题
如果你在多个地方重复使用样式,当你想修改某个属性或颜色时,就会变得难以维护------因为你需要逐一修改每一处使用该样式的代码。
Rich 提供了一个 Theme 类,可以用来定义自定义样式,并通过名称引用。这样你只需要在一个地方修改样式即可。
样式主题还能让代码更具语义性,例如使用 "warning" 这样的名称,比 "italic magenta underline" 更能表达意图。
要使用样式主题,可以创建一个 Theme 实例,并传入 Console 构造函数。例如:
python
from rich.console import Console
from rich.theme import Theme
custom_theme = Theme({
"info": "dim cyan",
"warning": "magenta",
"danger": "bold red"
})
console = Console(theme=custom_theme)
console.print("This is information", style="info")
console.print("[warning]The pod bay doors are locked[/warning]")
console.print("Something terrible happened!", style="danger")
注意
样式名称必须:
- 全部小写
- 以字母开头
- 只包含字母或字符
"."、"-"、"_"
自定义默认样式
Theme 类会继承 Rich 内置的默认样式。
如果你的自定义主题中包含已有样式的名称,则会覆盖默认样式。
这意味着你可以像定义新样式一样,轻松修改默认行为。例如,下面的代码修改了 Rich 对数字的高亮方式:
python
from rich.console import Console
from rich.theme import Theme
console = Console(theme=Theme({"repr.number": "bold green blink"}))
console.print("The total is 128")
你可以在 rich.theme.Theme 构造函数中设置 inherit=False,来禁用继承默认主题。
要查看默认主题,可以运行以下命令:
bash
python -m rich.theme
python -m rich.default_styles
加载主题
如果你更倾向于将样式写在外部配置文件中,而不是写在 Python 代码里,也可以这样做。格式如下:
ini
[styles]
info = dim cyan
warning = magenta
danger = bold red
你可以使用 read() 方法来读取这些配置文件。
控制台标记
Rich 支持一种简单的标记语法,你可以在几乎所有接受字符串的地方(例如 print() 和 log())插入颜色和样式。
可以运行以下命令查看示例:
bash
python -m rich.markup
语法
Console markup 使用一种类似 bbcode 的语法。如果你在方括号中写入样式(见 Styles),例如 [bold red],那么该样式会一直生效,直到遇到对应的关闭标签 [/bold red]。
示例:
python
from rich import print
print("[bold red]alert![/bold red] Something happened")
如果没有关闭标签,样式会持续到字符串末尾。有时候这在处理单行文本时会比较方便。例如:
python
print("[bold italic yellow on red blink]This text is impossible to read")
关闭标签也有简写形式。如果在关闭标签中省略样式名,Rich 会关闭最近一次开启的样式。例如:
python
print("[bold red]Bold and red[/] not bold or red")
这些标记标签可以相互组合使用,并且不需要严格嵌套。下面的示例演示了标签的交叉使用:
python
print("[bold]Bold[italic] bold and italic [/bold]italic[/italic]")
错误
如果标记中存在以下错误,Rich 会抛出 MarkupError:
- 标签不匹配,例如:
"[bold]Hello[/red]" - 没有匹配的关闭标签,例如:
"no tags[/]"
链接
Console markup 支持通过以下语法输出超链接:
text
[link=URL]text[/link]
示例:
python
print("Visit my [link=https://www.willmcgugan.com]blog[/link]!")
如果你的终端支持超链接,你可以点击 "blog" 打开浏览器;如果不支持,则只会显示文本但不可点击。
转义(Escaping)
有时你可能希望输出的内容不要被当作 markup 解析。可以在标签前加反斜杠进行转义。例如:
python
from rich import print
print(r"foo\[bar]")
输出:
text
foo[bar]
如果没有反斜杠,Rich 会将 [bar] 当作标签处理,并在不存在该样式时移除。
注意
如果你既想输出反斜杠,又不希望它转义标签,可以使用两个反斜杠。
函数 escape() 可以帮助你自动完成转义。
在动态生成字符串(例如使用 str.format 或 f-string)时,转义非常重要,否则可能会意外注入标签。例如:
python
def greet(name):
console.print(f"Hello {name}!")
调用:
python
greet("[blink]Gotcha![/blink]")
会产生闪烁文本,这可能不是你想要的。
解决方法是对输入进行转义:
python
from rich.markup import escape
def greet(name):
console.print(f"Hello {escape(name)}!")
表情符号
如果在 markup 中加入 emoji 代码,它会被替换为对应的 Unicode 字符。
emoji 代码由冒号包裹,例如:
python
from rich import print
print(":warning:")
输出:
text
⚠️
有些 emoji 有两种变体:
- emoji(彩色)
- text(单色)
可以通过添加 -emoji 或 -text 指定。例如:
python
from rich import print
print(":red_heart-emoji:")
print(":red_heart-text:")
查看所有可用 emoji:
bash
python -m rich.emoji
渲染标记
默认情况下,当你显式传入字符串给 print(),或者在其他可渲染对象(如 Table、Panel)中嵌入字符串时,Rich 会自动解析 markup。
如果 markup 语法与文本内容冲突,你可以关闭它:
python
print("...", markup=False)
或者在创建 Console 时关闭:
python
Console(markup=False)
标记 API
你可以使用 from_markup() 将字符串转换为带样式的文本,它会返回一个 Text 实例,你可以继续打印或添加样式。
富文本
Rich 提供了一个 Text 类,你可以用它为字符串添加颜色和样式属性。
在所有可以接受字符串的地方,你也可以使用 Text 实例,这让你对展示效果拥有更高的控制力。
你可以把这个类理解为:
👉 一个带有"样式标记区域"的字符串。
与内置的 str 不同,Text 是可变对象(mutable) ,并且大多数方法都是原地修改(in-place),而不是返回新对象。
一种给 Text 添加样式的方式是使用 stylize() 方法,它可以对指定范围(起始和结束偏移)应用样式。例如:
python
from rich.console import Console
from rich.text import Text
console = Console()
text = Text("Hello, World!")
text.stylize("bold magenta", 0, 6)
console.print(text)
这会输出 "Hello, World!",其中前 6 个字符(Hello)为加粗洋红色。
另一种方式是通过 append() 构造带样式的文本:
python
text = Text()
text.append("Hello", style="bold magenta")
text.append(" World!")
console.print(text)
如果你已经有带 ANSI 控制码的字符串,可以使用 from_ansi() 转换为 Text 对象:
python
text = Text.from_ansi("\033[1;35mHello\033[0m, World!")
console.print(text.spans)
ANSI控制码(ANSI Escape
Code)是一种用于控制终端显示的字符序列,通常以ESC(转义字符)开头,后面跟随特定的字符和参数。这些控制码可以用于改变文本的颜色、样式(如粗体、下划线)以及光标的位置等功能。ANSI控制码最早在20世纪70年代引入,成为终端设备的标准之一,至今仍被广泛使用。
常用的ANSI控制码
颜色设置: 通过ANSI控制码,可以设置文本的前景色和背景色。例如: \x1b[31m:设置文本颜色为红色。
\x1b[32m:设置文本颜色为绿色。 \x1b[0m:重置所有属性。 文本样式: 可以使用控制码来设置文本样式,例如:
\x1b[1m:设置为粗体。 \x1b[4m:设置为下划线。 \x1b[22m:重置为正常字体。 光标控制:
ANSI控制码还可以用于控制光标的位置,例如: \x1b[H:将光标移动到屏幕的左上角。
\x1b[10;10H:将光标移动到第10行第10列。
由于按片段构建 Text 很常见,Rich 提供了 assemble() 方法,可以组合字符串或(字符串 + 样式)对,并返回一个 Text 实例。例如:
python
text = Text.assemble(("Hello", "bold magenta"), ", World!")
console.print(text)
这个效果与上面的 ANSI 示例等价。
你还可以通过以下方法对文本进行高亮:
highlight_words():高亮指定单词highlight_regex():使用正则表达式高亮
文本属性
Text 类在构造时可以设置一些参数,用于控制显示方式:
-
justify:可选
"left"、"center"、"right"、"full",会覆盖默认对齐方式 -
overflow:可选
"fold"、"crop"、"ellipsis",会覆盖默认溢出处理 -
no_wrap:如果文本长度超过宽度,禁止自动换行
-
tab_size:设置 tab 的字符宽度
Text 实例几乎可以在 Rich API 的所有位置替代普通字符串,这让你可以精细控制文本在其他组件中的渲染方式。
例如,下面的代码会在 Panel 中将文本右对齐:
python
from rich import print
from rich.panel import Panel
from rich.text import Text
panel = Panel(Text("Hello", justify="right"))
print(panel)
👉
Text是"内容 + 样式"的载体👉
Panel、Table这些是"容器"这种分层其实已经非常接近 UI 系统的设计了(内容 vs 容器)。
高亮
Rich 会自动识别并高亮文本中的一些模式,例如:
-
数字
-
字符串
-
集合类型
-
布尔值
-
None
-
以及一些更复杂的模式,例如:
- 文件路径
- URL
- UUID
你可以通过以下方式关闭高亮:
- 在
print()或log()中设置highlight=False - 或在
Console构造函数中设置highlight=False(全局关闭)
如果在构造函数中关闭了高亮,你仍然可以在 print / log 中单独启用 highlight=True。
自定义高亮器
如果默认高亮不符合需求,你可以定义自己的高亮器。
最简单的方式是继承 RegexHighlighter 类,它会对匹配正则表达式的文本应用样式。
下面是一个示例:对"像邮箱地址的文本"进行高亮
python
from rich.console import Console
from rich.highlighter import RegexHighlighter
from rich.theme import Theme
class EmailHighlighter(RegexHighlighter):
"""对类似邮箱的文本应用样式"""
base_style = "example."
highlights = [r"(?P<email>[\w-]+@([\w-]+\.)+[\w-]+)"]
theme = Theme({"example.email": "bold magenta"})
console = Console(highlighter=EmailHighlighter(), theme=theme)
console.print("Send funds to money@example.org")

highlights 类变量需要包含一个正则表达式列表。
匹配表达式中的"命名分组"会被 base_style 前缀修饰,并作为样式使用。
在上面的例子中:
- 匹配到的邮箱 → 使用
example.email样式 - 该样式在自定义
Theme中定义为 "bold magenta"
如果你在 Console 中设置 highlighter,那么所有输出都会应用该高亮(如果启用)。
你也可以更细粒度地使用高亮器:直接调用实例并打印返回结果。例如:
python
console = Console(theme=theme)
highlight_emails = EmailHighlighter()
console.print(highlight_emails("Send funds to money@example.org"))
虽然 RegexHighlighter 很强大,但你也可以继承 Highlighter 基类来自定义高亮逻辑。
它包含一个 highlight 方法,接收需要高亮的 Text 对象。
下面是一个简单但"有点无聊"的例子:给每个字符随机上色
python
from random import randint
from rich import print
from rich.highlighter import Highlighter
class RainbowHighlighter(Highlighter):
def highlight(self, text):
for index in range(len(text)):
text.stylize(f"color({randint(16, 255)})", index, index + 1)
rainbow = RainbowHighlighter()
print(rainbow("I must not fear. Fear is the mind-killer."))
内置高亮器
Rich 提供了以下内置高亮器:
ISO8601Highlighter:高亮 ISO8601 日期时间字符串JSONHighlighter:高亮 JSON 格式字符串
美化打印
Rich 除了语法高亮之外,还会对容器类型进行格式化(即 pretty print),例如:
- list(列表)
- dict(字典)
- set(集合)
可以运行以下命令查看美化输出示例:
bash
python -m rich.pretty
注意:输出会根据终端宽度自动调整布局。
pprint 方法(pprint method)
pprint() 方法提供了一些额外参数,用于调整对象的美化输出方式。导入方式如下:
python
from rich.pretty import pprint
pprint(locals())
缩进指引线(Indent guides)
Rich 可以绘制缩进指引线,用来强调数据结构的层级关系,这对于深层嵌套结构特别有帮助。
pprint 方法默认启用缩进指引线,你可以通过设置 indent_guides=False 来关闭。
全部展开(Expand all)
Rich 通常会比较保守,只在一行内尽量多地展示内容。
如果你希望完全展开数据结构,可以设置 expand_all=True。例如:
python
pprint(["eggs", "ham"], expand_all=True)
截断美化输出(Truncating pretty output)
非常大的数据结构会影响可读性,甚至导致你在终端中翻很多页才能找到目标数据。
Rich 支持对容器和长字符串进行截断,以提供概览信息而不会淹没终端输出。
如果设置 max_length 为整数:
- 容器超过该长度会被截断
- 会显示
...以及未显示的元素数量
示例:
python
pprint(locals(), max_length=2)
如果设置 max_string 为整数:
- 字符串超过该长度会被截断
- 会显示未显示字符数量
示例:
python
pprint("Where there is a Will, there is a Way", max_string=21)
可渲染的 Pretty
Rich 提供了 Pretty 类,可以将美化后的数据嵌入到其他渲染组件中。
例如,在 Panel 中显示美化数据:
python
from rich import print
from rich.pretty import Pretty
from rich.panel import Panel
pretty = Pretty(locals())
panel = Panel(pretty)
print(panel)
Pretty 提供了大量参数用于调整格式化效果,详情可参考 Pretty 的完整文档。
Rich Repr 协议(Rich Repr Protocol)
Rich 能对任何输出进行语法高亮,但格式化能力主要限制在以下类型:
- 内置容器(list / dict / set 等)
- dataclass
- 以及 Rich 已知的对象(例如 attrs 库生成的对象)
如果你想让自定义对象也支持 Rich 的格式化,可以实现 Rich repr 协议。
可以运行以下命令查看示例:
bash
python -m rich.repr
先看一个普通类,它可能会从 Rich repr 中获益:
python
class Bird:
def __init__(self, name, eats=None, fly=True, extinct=False):
self.name = name
self.eats = list(eats) if eats else []
self.fly = fly
self.extinct = extinct
def __repr__(self):
return f"Bird({self.name!r}, eats={self.eats!r}, fly={self.fly!r}, extinct={self.extinct!r})"
python
BIRDS = {
"gull": Bird("gull", eats=["fish", "chips", "ice cream", "sausage rolls"]),
"penguin": Bird("penguin", eats=["fish"], fly=False),
"dodo": Bird("dodo", eats=["fruit"], fly=False, extinct=True)
}
print(BIRDS)
输出结果类似:
text
{'gull': Bird(...), 'penguin': Bird(...), 'dodo': Bird(...)}
这个输出的问题是:
- 太长,会换行
- repr 信息冗余(包含默认参数)
- 可读性较差
如果用 Rich 打印,会稍微好一些:
text
{
'gull': Bird(...),
'penguin': Bird(...),
'dodo': Bird(...)
}
虽然 Rich 能格式化 dict,但:
- repr 仍然冗长
- 输出仍然可能换行
- 默认参数仍然显示
我们可以为类添加 __rich_repr__ 方法:
python
def __rich_repr__(self):
yield self.name
yield "eats", self.eats
yield "fly", self.fly, True
yield "extinct", self.extinct, False
再次用 Rich 打印:
text
{
'gull': Bird(
'gull',
eats=['fish', 'chips', 'ice cream', 'sausage rolls']
),
'penguin': Bird('penguin', eats=['fish'], fly=False),
'dodo': Bird('dodo', eats=['fruit'], fly=False, extinct=True)
}
改进点:
- 默认值被省略
- 输出更紧凑
- 即使嵌套结构也仍然可读
python
{
'gull': Bird(
'gull',
eats=[
'fish',
'chips',
'ice cream',
'sausage rolls'
]
),
'penguin': Bird(
'penguin',
eats=['fish'],
fly=False
),
'dodo': Bird(
'dodo',
eats=['fruit'],
fly=False,
extinct=True
)
}
你可以在任何类中添加 __rich_repr__ 方法来启用 Rich 格式化。
这个方法应该返回一个可迭代对象(iterable),通常用 yield 实现会更方便(生成器形式)。
每个 yield 表示一个输出字段:
yield value→ 生成位置参数yield name, value→ 生成关键字参数yield name, value, default→ 如果 value != default 才输出
如果使用 None 作为 name,那么它会被当作位置参数处理,用于支持 tuple 类型的参数。
你也可以让 Rich 生成"尖括号风格"的 repr,这种风格通常用于无法轻易重建构造函数的对象。
做法是:
python
__rich_repr__.angular = True
这样输出会变成:
text
{
'gull': <Bird 'gull' eats=['fish', 'chips', 'ice cream', 'sausage rolls']>,
'penguin': <Bird 'penguin' eats=['fish'] fly=False>,
'dodo': <Bird 'dodo' eats=['fruit'] fly=False extinct=True>
}
注意,你可以在第三方库中添加 __rich_repr__,而不需要将 Rich 作为依赖。
如果没有安装 Rich,程序不会受到任何影响。
(💡 注:这个设计其实很工程化,本质是"可选增强协议",不会侵入原对象逻辑)
类型支持(Typing)
如果你想为 rich repr 添加类型标注,可以导入 rich.repr.Result:
python
import rich.repr
class Bird:
def __init__(self, name, eats=None, fly=True, extinct=False):
self.name = name
self.eats = list(eats) if eats else []
self.fly = fly
self.extinct = extinct
def __rich_repr__(self) -> rich.repr.Result:
yield self.name
yield "eats", self.eats
yield "fly", self.fly, True
yield "extinct", self.extinct, False
自动 Rich Repr(Automatic Rich Repr)
如果参数名与属性名一致,Rich 可以自动生成 repr。
使用 auto 装饰器:
python
import rich.repr
@rich.repr.auto
class Bird:
def __init__(self, name, eats=None, fly=True, extinct=False):
self.name = name
self.eats = list(eats) if eats else []
self.fly = fly
self.extinct = extinct
python
BIRDS = {
"gull": Bird("gull", eats=["fish", "chips", "ice cream", "sausage rolls"]),
"penguin": Bird("penguin", eats=["fish"], fly=False),
"dodo": Bird("dodo", eats=["fruit"], fly=False, extinct=True)
}
from rich import print
print(BIRDS)
需要注意的是:
- 这个装饰器会自动生成
__repr__ - 即使不使用 Rich 输出,也会影响默认 repr
如果你想生成"尖括号风格 repr",可以这样写:
python
@rich.repr.auto(angular=True)
class Bird:
def __init__(self, name, eats=None, fly=True, extinct=False):
self.name = name
self.eats = list(eats) if eats else []
self.fly = fly
self.extinct = False
日志处理器(Logging Handler)
Rich 提供了一个 logging handler(日志处理器),可以对 Python logging 模块输出的文本进行格式化与彩色高亮。
下面是一个基础的 Rich logger 配置示例:
python
import logging
from rich.logging import RichHandler
FORMAT = "%(message)s"
logging.basicConfig(
level="NOTSET",
format=FORMAT,
datefmt="[%X]",
handlers=[RichHandler()]
)
log = logging.getLogger("rich")
log.info("Hello, World!")
这里的核心点是:
RichHandler()接管 logging 输出- 终端日志会自动变成"带颜色 + 更可读"的格式
- 本质是"把 logging 输出重新渲染成 Rich renderable"
默认情况下,Rich 的 logging 不会解析 Console Markup。
原因是:
大多数 logging 库不会自动转义
[],直接开启 markup 容易引发冲突或注入问题。
如果你确实需要,可以在 handler 上启用:
python
RichHandler(markup=True)
或者在单条日志中启用:
python
log.error(
"[bold red blink]Server is shutting down![/]",
extra={"markup": True}
)
你也可以关闭单条日志的高亮:
python
log.error("123 will not be highlighted", extra={"highlighter": None})
异常处理(Handle exceptions)
Rich 的 RichHandler 可以结合 Rich 的 Traceback 来美化异常输出,比 Python 默认 traceback 更详细。
启用方式:
python
import logging
from rich.logging import RichHandler
logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(rich_tracebacks=True)]
)
log = logging.getLogger("rich")
try:
print(1 / 0)
except Exception:
log.exception("unable print!")
RichHandler 还有很多可配置项,可以参考官方 reference。
抑制框架栈(Suppressing Frames)
如果你在使用框架(例如 click、django 等),通常你只关心:
"你自己写的代码出了什么问题"
而不想看到框架内部调用栈。
可以通过 tracebacks_suppress 参数屏蔽框架代码:
python
import click
import logging
from rich.logging import RichHandler
logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
handlers=[
RichHandler(
rich_tracebacks=True,
tracebacks_suppress=[click]
)
]
)
被 suppress 的 frame 会变成:
- 只显示文件名
- 只显示行号
- 不显示源码内容
异常回溯(Traceback)
Rich 可以对 Python traceback(异常回溯)进行语法高亮与结构化格式化。
相比标准 Python traceback,它的特点是:
- 更易读
- 展示更多上下文代码
- 层级结构更清晰
运行以下命令可以看到 Rich traceback 示例:
bash
python -m rich.traceback
打印异常(Printing tracebacks)
print_exception() 方法可以打印当前正在处理的异常。
python
from rich.console import Console
console = Console()
try:
do_something()
except Exception:
console.print_exception(show_locals=True)
show_locals=True
会在每一层 stack frame 中显示局部变量的值
💡(经验提示):这个功能在 debug 复杂状态流时非常有用,尤其是多层函数调用 + 状态依赖场景。
Traceback Handler(全局异常处理器)
Rich 可以安装为默认异常处理器,让所有未捕获异常都自动美化输出:
python
from rich.traceback import install
install(show_locals=True)
install() 支持多个参数配置 traceback 行为(详见官方 install 文档)。
自动安装 Traceback(Automatic Traceback Handler)
在某些情况下,你可能希望:
不修改业务代码,也能自动启用 Rich traceback
可以通过 sitecustomize.py 实现。
文件路径示例:
./.venv/lib/python3.9/site-packages/sitecustomize.py
如果文件不存在,可以创建:
bash
touch .venv/lib/python3.9/site-packages/sitecustomize.py
然后写入:
python
from rich.traceback import install
install(show_locals=True)
一旦配置完成:
- 虚拟环境内所有 Python 程序
- 都会自动使用 Rich traceback
💡这个机制本质是利用 Python import hook,在解释器启动阶段"注入调试能力",适合内部环境,但不太建议在对外发布的项目里默认开启。
注意事项,如果是对外项目:
建议在 main entry point 中显式调用 install()
而不是依赖 sitecustomize。
抑制框架栈(Suppressing Frames)
在使用框架(click / django 等)时,通常只关心:
自己业务代码的 traceback
可以过滤框架代码:
python
import click
from rich.traceback import install
install(suppress=[click])
也可以用于:
TracebackinstallConsole.print_exceptionRichHandler
被 suppress 的 frame 会变成:
- 只显示文件名
- 只显示行号
- 不显示代码内容
最大帧数(Max Frames)
递归错误可能产生非常大的 traceback:
- 渲染慢
- 重复帧很多
- 可读性差
Rich 默认限制:
max_frames = 100
行为:
- 前 50 帧
- 后 50 帧
可以关闭限制:
python
console.print_exception(max_frames=0)
或设置限制数量:
python
console.print_exception(max_frames=20)
示例:递归错误
python
from rich.console import Console
def foo(n):
return bar(n)
def bar(n):
return foo(n)
console = Console()
try:
foo(1)
except Exception:
console.print_exception(max_frames=20)
提示输入(Prompt)
Rich 提供了一组 Prompt 类,用于向用户请求输入,并会循环询问直到得到有效输入为止。
这些 Prompt 内部都基于 Console API 实现。
python
from rich.prompt import Prompt
name = Prompt.ask("Enter your name")
提示内容可以是:
- 普通字符串(支持 Console Markup 和 emoji)
- 或
Text对象
💡Prompt 本质就是一个"带验证循环的输入封装器",比 input() 多了规则控制能力。
如果用户直接回车不输入内容,可以返回默认值:
python
from rich.prompt import Prompt
name = Prompt.ask("Enter your name", default="Paul Atreides")
选项限制(choices):
如果提供 choices,用户必须输入其中之一,否则会反复提示:
python
from rich.prompt import Prompt
name = Prompt.ask(
"Enter your name",
choices=["Paul", "Jessica", "Duncan"],
default="Paul"
)
大小写控制(case sensitivity):
默认是区分大小写的:
python
from rich.prompt import Prompt
name = Prompt.ask(
"Enter your name",
choices=["Paul", "Jessica", "Duncan"],
default="Paul",
case_sensitive=False
)
这样:
Paulpaul
都会被接受。
Rich 还提供了类型化输入:
整数输入:
python
from rich.prompt import IntPrompt
value = IntPrompt.ask("Enter a number")
浮点数输入:
python
from rich.prompt import FloatPrompt
value = FloatPrompt.ask("Enter a float")
确认类(Confirm):
Confirm 用于简单的 yes / no 问题:
python
from rich.prompt import Confirm
is_rich_great = Confirm.ask("Do you like rich?")
assert is_rich_great
Confirm 本质是:
"带默认解析规则的二值 Prompt"
通常会解析:
- y / yes → True
- n / no → False
Prompt 类是可继承的,设计目标就是可定制:
- 自定义输入规则
- 自定义校验逻辑
- 自定义提示格式
相关示例可以查看 prompt.py
运行:
bash
python -m rich.prompt
可以看到所有 Prompt 的实际交互效果。
列布局(Columns)
Rich 可以使用 Columns 类,将文本或其他 Rich 可渲染对象以整齐的"多列布局"方式展示。
使用方法是:
- 构造
Columns对象 - 传入一个可迭代的 renderables(可渲染对象)
- 打印到 Console
下面这个例子模拟了 Linux / macOS 中的 ls 命令,用于列出目录内容:
python
import os
import sys
from rich import print
from rich.columns import Columns
if len(sys.argv) < 2:
print("Usage: python columns.py DIRECTORY")
else:
directory = os.listdir(sys.argv[1])
columns = Columns(directory, equal=True, expand=True)
print(columns)
-
equal=True→ 每一列宽度尽量一致(强制对齐视觉)
-
expand=True→ 尽可能占满终端宽度(类似"自适应布局")
Columns 本质就是一个:
"终端版 flex/grid 布局器"
它做的事情不是排序数据,而是把线性数据重新排版成视觉网格。
Rich 的 Columns 不仅可以显示字符串,还可以显示:
- Table
- Panel
- Syntax 高亮代码
- 自定义 renderable 对象
📌 官方也提供了一个更复杂的示例:
columns.py
用于展示"多类型 renderable 混排列布局"。
渲染组(Render Groups)
Rich 提供了 Group 类,用于将多个 renderable(可渲染对象)组合在一起,使它们可以在"只接受单个 renderable 的上下文"中一起渲染。
有些组件(例如 Panel)一次只能接收一个 renderable,但你又想放多个内容,这时候就需要 Group。
示例:在 Panel 中嵌套多个 Panel
python
from rich import print
from rich.console import Group
from rich.panel import Panel
panel_group = Group(
Panel("Hello", style="on blue"),
Panel("World", style="on red"),
)
print(Panel(panel_group))
这个结构的含义可以理解为:
Group = "把多个 UI 块打包成一个整体"
然后再交给外层 Panel 渲染。
这其实就是一个"渲染层级容器":
- Panel 负责"边框 UI"
- Group 负责"子组件组合"
- Rich renderer 负责递归渲染树结构
如果 renderables 是动态生成的:
- 手动写 Group 会变得很麻烦
- 尤其是数量较多或来源是循环时
Rich 提供了 group() 装饰器,用来从迭代器构建 Group。
示例:用装饰器构建 Group
python
from rich import print
from rich.console import group
from rich.panel import Panel
@group()
def get_panels():
yield Panel("Hello", style="on blue")
yield Panel("World", style="on red")
print(Panel(get_panels()))
本质区别(理解重点):
手动 Group:
- 适合"静态 UI"
- 结构固定
- 适合 demo 或布局简单场景
group() 装饰器:
- 适合"动态 UI"
- 支持循环生成
- 更像 UI pipeline(流式构建)
Markdown 渲染(Markdown)
Rich 可以将 Markdown 渲染到终端中显示。
使用方式是:
- 构造
Markdown对象 - 交给 Console 打印
Markdown 是一种非常适合在命令行程序中展示结构化内容的方式。
示例用法:
python
MARKDOWN = """
# This is an h1
Rich can do a pretty *decent* job of rendering markdown.
1. This is a list item
2. This is another list item
"""
from rich.console import Console
from rich.markdown import Markdown
console = Console()
md = Markdown(MARKDOWN)
console.print(md)
特性说明:
✔ 支持标题
例如:
md
# H1
## H2
✔ 支持列表
md
1. item one
2. item two
✔ 支持强调
md
*italic*
**bold**
✔ 支持代码块(重点)
Rich 的一个关键能力是:
Markdown 里的代码块会自动进行语法高亮
这在 CLI 工具里非常实用。
你也可以直接在命令行渲染 Markdown 文件:
bash
python -m rich.markdown README.md
查看帮助信息:
bash
python -m rich.markdown -h
Rich 的 Markdown 支持本质是:
把"文档系统"直接搬进终端 UI
它让 CLI 程序可以天然具备:
- 文档展示能力
- 结构化排版能力
- 代码高亮能力
属于 CLI 体验升级的核心模块之一。
内边距(Padding)
Rich 提供了 Padding 类,用于在文本或其他 renderable(可渲染对象)周围添加空白区域。
下面这个例子会在 "Hello" 周围添加 1 个字符的 padding:
- 上下各一行空白
- 左右各一个空格
python
from rich import print
from rich.padding import Padding
test = Padding("Hello", 1)
print(test)

Rich 支持用 tuple 精细控制 padding,规则类似 CSS 的 margin / padding 写法。
2 个值的情况:
python
Padding("Hello", (2, 4))
含义:
- 2 → 上 / 下
- 4 → 左 / 右
效果:
- 上下各 2 行空白
- 左右各 4 个空格
4 个值的情况:
python
(top, right, bottom, left)
即分别控制四个方向。
这个设计基本就是:
"把 UI spacing 抽象成 CSS box model"
Rich 的 Padding 还支持 style 参数,用来控制背景色或整体样式。
示例:带背景色的 padding
python
from rich import print
from rich.padding import Padding
test = Padding("Hello", (2, 4), style="on blue", expand=False)
print(test)
-
expand=True(默认行为之一)→ padding 会扩展到终端全宽
-
expand=False→ 不强制填满终端宽度
这个参数本质就是:
控制"这个 UI block 是流式布局还是固定内容块"
Rich 的 Padding 可以用在任何 renderable 上,例如:
- Table 行高调整
- Panel 内边距控制
- 强调某一条日志或数据
示例思路:
在 Table 中强调某一行:
text
Padding + background color = "高亮行效果"
面板(Panel)
Rich 可以使用 Panel 来为文本或其他 renderable 绘制一个边框容器。
基本用法,将 renderable 作为第一个参数传入 Panel:
python
from rich import print
from rich.panel import Panel
print(Panel("Hello, [red]World!"))
Rich 允许你通过 box 参数改变边框样式。
不同的 box 样式可以参考 Box 定义(例如不同风格的线框、双线框等)。
默认情况下:
Panel 会自动扩展到终端的全宽
也就是说它是"block-level UI"。
如果你不想让 Panel 撑满整行,可以:
方法 1:expand=False
python
from rich import print
from rich.panel import Panel
print(Panel("Hello, [red]World!", expand=False))
方法 2:fit()
python
from rich import print
from rich.panel import Panel
print(Panel.fit("Hello, [red]World!"))
Rich 的 Panel 支持:
- title(顶部标题)
- subtitle(底部副标题)
示例:
python
from rich import print
from rich.panel import Panel
print(Panel(
"Hello, [red]World!",
title="Welcome",
subtitle="Thank you"
))

Rich 的 Panel 本质是:
一个"终端 UI 的卡片容器(Card Component)"
它对应到前端概念就是:
- Card / Container
- 带 border + title + layout control
- 可嵌套 renderable
如果你把 Rich 当成"终端 UI 框架",Panel 就是最基础的 UI 盒子组件之一。
明白,这次问题不在"翻译",而在输出形态被我拆成了"讲解稿"而不是"文档翻译"。
你要的是:
✔ 结构完整
✔ 段落连续
✔ 不被拆碎
✔ 允许少量注释,但不能破坏原文流
进度显示(Progress Display)
Rich 可以用于展示长时间运行任务(如文件复制、下载等)的实时进度信息。显示内容是可配置的,默认包括任务描述、进度条、完成百分比以及预计剩余时间。
Rich 的进度系统支持多个任务同时显示,每个任务都有自己的进度条与状态信息。你可以用它来追踪线程或进程中的并发任务。
如果想查看效果,可以在命令行运行:
bash
python -m rich.progress
注意:Progress 在 Jupyter Notebook 中也可以使用,但不会自动刷新,需要手动调用
refresh(),或者在update()时设置refresh=True。也可以使用track(),它会在每次循环中自动刷新。
基本用法
最简单的使用方式是调用 track() 函数,它接受一个序列(如 list 或 range),以及一个可选的任务描述。track() 会在遍历序列时自动更新进度。
python
import time
from rich.progress import track
for i in track(range(20), description="Processing..."):
time.sleep(1)
高级用法
如果你需要多个任务,或者想自定义进度条的列结构,可以直接使用 Progress 类。
创建 Progress 对象后,通过 add_task() 添加任务,通过 update() 更新进度。
Progress 通常作为上下文管理器使用,会自动开始和结束进度显示。
python
import time
from rich.progress import Progress
with Progress() as progress:
task1 = progress.add_task("[red]Downloading...", total=1000)
task2 = progress.add_task("[green]Processing...", total=1000)
task3 = progress.add_task("[cyan]Cooking...", total=1000)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
progress.update(task3, advance=0.9)
time.sleep(0.02)
这里 total 表示完成 100% 所需的总步数。这个"步数"是业务定义的,比如文件字节数、图片数量或处理次数。
启动与停止
推荐使用上下文管理器。如果不使用,则必须手动调用 start() 和 stop()。
python
import time
from rich.progress import Progress
progress = Progress()
progress.start()
try:
task1 = progress.add_task("[red]Downloading...", total=1000)
task2 = progress.add_task("[green]Processing...", total=1000)
task3 = progress.add_task("[cyan]Cooking...", total=1000)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
progress.update(task3, advance=0.9)
time.sleep(0.02)
finally:
progress.stop()
这里必须使用 try/finally,确保即使异常发生也能正确关闭进度显示。
更新任务
add_task() 会返回一个 Task ID,用于后续更新。
更新任务时通常有两种方式:
completed:直接设置完成进度advance:在当前基础上增加进度
此外,update() 还支持传入额外字段,这些字段会存入 task.fields,可用于自定义显示列。
隐藏任务
任务默认是可见的,也可以通过 visible=False 创建隐藏任务,或动态控制可见性。
临时进度(Transient)
默认情况下,退出进度上下文后,最后一帧会保留在终端中。
如果设置 transient=True,退出时进度条会自动清除:
python
with Progress(transient=True) as progress:
task = progress.add_task("Working", total=100)
do_work(task)
这种模式适合需要"干净输出"的 CLI 工具。
不确定进度(Indeterminate)
如果任务开始时无法确定总量(例如等待服务器响应或扫描文件数量),可以使用不确定模式:
start=False- 或
total=None
此时会显示一个"呼吸式动画",表示任务正在进行但进度未知。
当确定步数后,可以调用 start_task(),再进入正常进度更新流程。
自动刷新(Auto refresh)
Rich 的进度系统默认会以 每秒 10 次 的频率刷新进度信息。
你可以通过 Progress 构造函数中的 refresh_per_second 参数来调整刷新频率。
如果你知道更新不会那么频繁,建议把这个值调低,以减少性能开销。
如果你的任务更新非常不频繁,也可以完全关闭自动刷新:
python
auto_refresh=False
关闭后,你需要在每次更新任务后手动调用 refresh() 来刷新界面。
展开模式(Expand)
默认情况下,进度条只会占用显示任务信息所需的最小宽度。
如果设置:
python
expand=True
Rich 会将整个进度显示扩展到终端的全宽。
列(Columns)
进度显示的结构可以通过 Progress 构造函数的位置参数来自定义,这些参数被称为"列"。
列可以是:
- 格式字符串(format string)
- 或
ProgressColumn对象
格式字符串中可以访问一个 task 对象,例如:
{task.description}→ 任务描述{task.completed} of {task.total}→ 已完成 / 总数
通过 update() 传入的额外字段会存储在:
task.fields
访问方式:
text
{task.fields[extra]}
Rich 的默认 Progress 等价于:
python
progress = Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
TimeRemainingColumn(),
)
如果你想在默认列基础上增加内容,可以使用:
python
Progress.get_default_columns()
示例:
python
progress = Progress(
SpinnerColumn(),
*Progress.get_default_columns(),
TimeElapsedColumn(),
)
Rich 提供的内置列包括:
BarColumn→ 进度条TextColumn→ 文本信息TimeElapsedColumn→ 已用时间TimeRemainingColumn→ 预计剩余时间MofNCompleteColumn→ "已完成/总数"FileSizeColumn→ 文件大小进度TotalFileSizeColumn→ 总文件大小DownloadColumn→ 下载进度TransferSpeedColumn→ 传输速度SpinnerColumn→ 动画 spinnerRenderableColumn→ 自定义 renderable
自定义表格列宽(Table Columns)
Rich 在内部会用 Table 来渲染任务列表。
你可以通过 Column(ratio=...) 控制各列占比。
示例:
python
from time import sleep
from rich.table import Column
from rich.progress import Progress, BarColumn, TextColumn
text_column = TextColumn("{task.description}", table_column=Column(ratio=1))
bar_column = BarColumn(bar_width=None, table_column=Column(ratio=2))
progress = Progress(text_column, bar_column, expand=True)
with progress:
for n in progress.track(range(100)):
sleep(0.1)
打印 / 日志输出(Print / Log)
Rich 的 Progress 内部会创建一个 Console 对象:
python
progress.console
你可以直接在进度运行时打印内容,它会显示在进度条上方:
python
with Progress() as progress:
task = progress.add_task("working", total=10)
for job in range(10):
progress.console.print(f"Working on job #{job}")
progress.advance(task)
也可以传入你自己的 Console:
python
with Progress(console=my_console) as progress:
my_console.print("Starting work")
stdout / stderr 重定向
Rich 默认会重定向:
- stdout
- stderr
这样可以避免 print() 破坏进度条布局。
可以关闭:
python
redirect_stdout=False
redirect_stderr=False
自定义进度显示(Customization)
如果默认 Progress 不满足需求,可以重写:
python
get_renderables()
示例:
python
from rich.panel import Panel
from rich.progress import Progress
class MyProgress(Progress):
def get_renderables(self):
yield Panel(self.make_tasks_table(self.tasks))
文件读取进度
Rich 支持直接对文件读取加进度条。
读取文件(open)
python
import json
import rich.progress
with rich.progress.open("data.json", "rb") as file:
data = json.load(file)
包装已有文件对象(wrap_file)
python
from urllib.request import urlopen
from rich.progress import wrap_file
response = urlopen("https://www.textualize.io")
size = int(response.headers["Content-Length"])
with wrap_file(response, size) as file:
for line in file:
print(line.decode())
Rich 的 Progress 本质是:
一个"可插拔列系统 + 实时刷新渲染器"的终端任务 UI 框架
核心能力:
- 多任务并发展示
- 可扩展 UI 列
- 文件 / 流进度包装
- stdout/stderr 不干扰 UI
- 高度可定制布局
嵌套进度条(Nesting Progress Bars)
Rich 支持在一个进度任务的上下文中再创建新的进度条。
当你在已有进度条(通过 track() 或 Progress 上下文)内部再创建一个进度条时,子进度条会被显示在主进度条下方。
嵌套示例:
python
from rich.progress import track
from time import sleep
for count in track(range(10)):
for letter in track("ABCDEF", transient=True):
print(f"Stage {count}{letter}")
sleep(0.1)
sleep(0.1)
行为说明:
- 外层
track(range(10))是主进度条 - 内层
track("ABCDEF")会生成子进度条 - 子进度条显示在主进度条下方
嵌套进度条的刷新频率:
由外层进度条的
refresh_per_second控制
也就是说:
- 外层 = 调度器
- 内层 = 被调度 UI
多 Progress 实例(Multiple Progress)
Rich 不支持在单个 Progress 实例中为不同任务设置不同列布局。
但你可以:
同时创建多个 Progress 实例
并将它们放入 Live Display 中。
相关示例:
live_progress.pydynamic_progress.py
语法高亮(Syntax)
Rich 支持对多种编程语言进行语法高亮显示,并且可以显示行号。
要进行语法高亮,需要创建一个 Syntax 对象,然后打印到控制台:
python
from rich.console import Console
from rich.syntax import Syntax
console = Console()
with open("syntax.py", "rt") as code_file:
syntax = Syntax(code_file.read(), "python")
console.print(syntax)
Rich 提供了更方便的构造方式 from_path(),可以直接从磁盘读取文件并自动识别类型。
上面的例子可以写成:
python
from rich.console import Console
from rich.syntax import Syntax
console = Console()
syntax = Syntax.from_path("syntax.py")
console.print(syntax)
如果设置:
python
line_numbers=True
Rich 会在左侧显示行号列。
Syntax 构造函数(以及 from_path())支持 theme 参数。
它接受:
-
Pygments 主题名称
-
或特殊主题:
ansi_darkansi_light
这些会使用终端默认的颜色方案。
你可以覆盖主题背景色:
python
background_color="red"
background_color="#ff0000"
background_color="rgb(255,0,0)"
也可以设置:
text
"default"
表示使用终端默认背景色。
Rich 也支持直接在命令行使用:
bash
python -m rich.syntax syntax.py
查看完整参数:
bash
python -m rich.syntax -h
Rich 的 Syntax 模块本质是:
一个"终端代码渲染器(syntax highlighting renderer)"
能力包括:
- 多语言高亮(基于 Pygments)
- 行号支持
- 主题系统
- 背景色控制
- CLI 直接使用
表格(Tables)
Rich 的 Table 类提供了多种方式将表格数据渲染到终端。
要渲染表格,需要先创建 Table 对象,通过 add_column() 添加列,再通过 add_row() 添加行,最后打印到控制台。
示例:
python
from rich.console import Console
from rich.table import Table
table = Table(title="Star Wars Movies")
table.add_column("Released", justify="right", style="cyan", no_wrap=True)
table.add_column("Title", style="magenta")
table.add_column("Box Office", justify="right", style="green")
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
console = Console()
console.print(table)
输出效果如下:

Rich 会自动计算每一列的最优宽度,以适配内容;如果终端宽度不够,会自动换行。
表格特性(Table Options)
Table 构造函数支持很多参数,用来控制整体样式:
title:表格标题(顶部)caption:表格注释(底部)width:固定宽度(关闭自动计算)min_width:最小宽度box:边框样式(见 Box)safe_box:强制 ASCII 边框(兼容旧终端)padding:单元格内边距(支持 Padding 语义)collapse_padding:合并相邻单元格 paddingpad_edge:是否保留边缘 paddingexpand:是否撑满终端宽度show_header:是否显示表头show_footer:是否显示表尾show_edge:是否显示外边框show_lines:是否显示行间分隔线leading:行与行之间额外间距style:整体样式row_styles:行交替样式(类似 zebra stripe)header_style/footer_styleborder_styletitle_style/caption_styletitle_justify/caption_justifyhighlight:是否自动高亮内容
边框样式(Border Styles)
可以通过 Box 预设修改边框风格:
python
from rich import box
table = Table(title="Star Wars Movies", box=box.MINIMAL_DOUBLE_HEAD)
也可以直接:
python
box=None
👉 这时表格会变成"无边框输出",适合嵌入式 UI。
行线(Lines)
默认只有表头下有一条线。
如果想每行都有分隔线:
python
show_lines=True
或者:
end_section=True:强制插入分割线add_section():手动插入分割线
空表处理(Empty Tables)
没有列的表打印结果是空行。
建议动态处理:
python
if table.columns:
print(table)
else:
print("[i]No data...[/i]")
添加列(Adding Columns)
可以在构造函数直接写列名:
python
table = Table("Released", "Title", "Box Office", title="Star Wars Movies")
如果需要更细控制:
python
from rich.table import Column, Table
table = Table(
"Released",
"Title",
Column(header="Box Office", justify="right"),
title="Star Wars Movies"
)
列选项(Column Options)
每一列也有独立配置:
header_style:表头样式footer_stylestyle:整列样式(比如整列高亮)justify:对齐方式(left/center/right/full)vertical:垂直对齐(top/middle/bottom)width:固定列宽min_width/max_widthratio:宽度比例分配(类似 flex)no_wrap:禁止换行highlight:自动语法高亮
垂直对齐(Vertical Alignment)
可以按列设置:
python
vertical="middle"
也可以单独控制某个 cell:
python
from rich.align import Align
table.add_row(Align("Title", vertical="middle"))
网格布局(Grids)
Table 不只是"表格",还能当布局系统用(类似 CLI UI layout engine)。
关闭表头和边框后可以做排版:
python
from rich.table import Table
grid = Table.grid(expand=True)
grid.add_column()
grid.add_column(justify="right")
grid.add_row("Raising shields", "[bold magenta]COMPLETED [green]:heavy_check_mark:")
print(grid)
树形结构(Tree)
Rich 提供了一个 Tree 类,可以在终端中生成树状视图。树形结构非常适合展示文件系统目录结构,或任何层级(hierarchical)数据。
树的每个分支都可以带有标签(label),标签可以是普通文本,也可以是任意 Rich 可渲染对象(renderable)。
可以运行下面命令查看 Tree 示例:
bash
python -m rich.tree
下面代码创建一个带简单文本标签的树并打印:
python
from rich.tree import Tree
from rich import print
tree = Tree("Rich Tree")
print(tree)
此时只有一个节点,因此输出仅是:
Rich Tree
真正有用的是通过 add() 添加子节点:
python
tree.add("foo")
tree.add("bar")
print(tree)
此时树会出现两个分支,并通过"连接线"与根节点关联。
add() 返回的是一个新的 Tree 实例,因此可以继续在子节点上扩展,形成递归结构:
python
baz_tree = tree.add("baz")
baz_tree.add("[red]Red").add("[green]Green").add("[blue]Blue")
print(tree)
树样式(Tree Styles)
Tree 构造函数和 add() 方法支持两个关键样式参数:
style:作用于整个分支guide_style:作用于连接线(树枝线)
这些样式会继承到子树节点。
例如:
guide_style="bold":使用更粗的 Unicode 树线guide_style="underline2":使用双线风格
如果设置不同 guide_style,树的"结构线条"视觉风格会完全不同,这在终端 UI 设计中非常重要(尤其是做 CLI dashboard 时)。
更实用的例子可以参考:
bash
tree.py
它可以生成磁盘目录结构的树形视图,用于展示文件系统层级结构。
Live 显示(Live Display)
进度条和状态指示器本质上都依赖"实时刷新终端"的能力。Rich 提供 Live 类,用于构建自定义的实时界面(live display)。
运行以下命令可以看到 Live 的演示:
bash
python -m rich.live
📌 注意:如果看到 "..." 省略号,说明终端高度不够,内容被截断了。
基本用法(Basic Usage)
创建 Live 对象时传入一个 renderable(可渲染对象),并用上下文管理器启动:
python
import time
from rich.live import Live
from rich.table import Table
table = Table()
table.add_column("Row ID")
table.add_column("Description")
table.add_column("Level")
with Live(table, refresh_per_second=4): # 每秒刷新4次(视觉更流畅)
for row in range(12):
time.sleep(0.4)
table.add_row(f"{row}", f"description {row}", "[red]ERROR")
👉 核心理解:
Live 的本质是一个"不断重绘 renderable 的循环刷新器",不是线程UI,而是"终端帧刷新"。
动态更新 renderable(Updating Renderable)
你也可以完全替换 renderable,而不是只修改内容:
python
import random
import time
from rich.live import Live
from rich.table import Table
def generate_table() -> Table:
table = Table()
table.add_column("ID")
table.add_column("Value")
table.add_column("Status")
for row in range(random.randint(2, 6)):
value = random.random() * 100
table.add_row(
f"{row}",
f"{value:3.2f}",
"[red]ERROR" if value < 50 else "[green]SUCCESS"
)
return table
with Live(generate_table(), refresh_per_second=4) as live:
for _ in range(40):
time.sleep(0.4)
live.update(generate_table())
全屏模式(Alternate Screen)
设置 screen=True 可以进入"终端替代屏幕":
- Live 占满整个屏幕
- 退出后恢复原终端
适合做 CLI 应用 / dashboard / pseudo GUI。
临时显示(Transient Display)
默认退出 Live 后,最后一帧会留在终端。
如果希望"退出即清空":
python
Live(..., transient=True)
自动刷新(Auto Refresh)
默认刷新频率:4 FPS
可通过:
python
refresh_per_second
调整。
如果:
python
auto_refresh=False
则需要手动调用:
refresh()- 或
update(..., refresh=True)
👉 实战建议:
低频更新(比如 IO/网络)建议关闭 auto refresh,避免 CPU 空转。
垂直溢出(Vertical Overflow)
当内容比终端高时:
"crop":直接截断"ellipsis":最后一行显示 "..."(默认)"visible":完整显示(但无法正确清屏)
👉 经验理解:
visible 适合调试,不适合生产 CLI UI。
print / log 输出
Live 内部有一个 Console:
python
live.console
可以在 Live 运行时打印日志:
python
live.console.print("Working...")
输出会显示在 Live 上方。
stdout / stderr 重定向
为了避免 print() 干扰 UI:
- 默认会重定向 stdout / stderr
- 可以关闭:
python
redirect_stdout=False
redirect_stderr=False
嵌套 Live(Nesting)
可以在一个 Live 中嵌套另一个 Live:
- 内层内容显示在外层下方
- 早期版本会直接报错(LiveError)
更复杂案例:
table_movie.pytop_lite_simulator.py
用于展示多 Live + 动态 UI 的实际应用。
布局(Layout)
Rich 提供了 Layout 类,可以将终端屏幕划分为多个区域(分区),每个区域可以独立渲染不同内容。
它既可以和 Live Display 一起用于构建全屏 CLI 应用,也可以单独使用。
运行下面命令可以查看 Layout 示例:
bash
python -m rich.layout
创建布局(Creating Layouts)
创建 Layout 并打印:
python
from rich import print
from rich.layout import Layout
layout = Layout()
print(layout)
此时会看到一个"占位框",因为还没有任何内容。
布局(Layout)
Rich 提供了一个 Layout 类,可以将屏幕区域划分为多个部分,每个部分可以包含独立内容。它可以和 Live Display 一起使用来创建全屏"应用程序",也可以单独使用。
要查看 Layout 示例,可以在命令行运行:
bash
python -m rich.layout
创建布局
要定义一个布局,先构造一个 Layout 对象并打印:
python
from rich import print
from rich.layout import Layout
layout = Layout()
print(layout)
这会绘制一个与终端大小相同的框,并显示一些布局信息。
这个框是一个"占位符",因为我们还没有添加任何内容。
在继续之前,我们通过 split_column() 方法把布局拆分成两个子布局:
python
layout.split_column(
Layout(name="upper"),
Layout(name="lower")
)
print(layout)
这会把屏幕分成上下两个等分区域。
name 属性是内部标识符,用于后续访问子布局。
接下来我们继续拆分 lower,这次使用 split_row():
python
layout["lower"].split_row(
Layout(name="left"),
Layout(name="right"),
)
print(layout)
此时屏幕会被分成三个区域:
- 上半部分 upper
- 下半部分再分为 left / right 两个四分区

你可以继续这样调用 split(),不断拆分出更多区域。
设置渲染内容(Renderables)
Layout 的第一个位置参数可以是任意 Rich renderable,它会自动适配布局区域。
例如,我们可以把右侧拆成两个面板:
python
from rich.panel import Panel
layout["right"].split(
Layout(Panel("Hello")),
Layout(Panel("World!"))
)
你也可以用 update() 替换当前内容:
python
layout["left"].update(
"The mystery of life isn't a problem to solve, but a reality to experience."
)
print(layout)
固定大小(Fixed size)
可以设置固定尺寸:
python
layout["upper"].size = 10
print(layout)
这会让 upper 区域始终占 10 行,不受终端大小影响。
如果是水平布局,则 size 表示字符宽度,而不是行数。
比例布局(Ratio)
除了固定大小,还可以使用比例布局:
python
layout["upper"].size = None
layout["upper"].ratio = 2
print(layout)
这会让 upper 占据 2/3 的空间。
原因是:
- lower 默认 ratio = 1
- 总比例 = 2 + 1 = 3
所以:
- upper = 2/3
- lower = 1/3
比例布局也可以配合最小尺寸:
python
layout["lower"].minimum_size = 10
用于防止布局被压缩得过小。
可见性(Visibility)
可以隐藏某个区域:
python
layout["upper"].visible = False
print(layout)
隐藏后 lower 会自动扩展填充空间。
恢复:
python
layout["upper"].visible = True
print(layout)
结构树(Tree)
可以用 tree 属性查看布局结构:
python
print(layout.tree)
用于调试复杂布局结构。
完整全屏应用示例:
bash
python -m rich.layout
参考:
控制台协议
Rich 支持一种简单的"协议机制",让自定义对象也能拥有富文本渲染能力,这样你在 print() 或 log() 时,就可以直接输出带颜色、样式和结构化信息的对象。
这种机制的价值在于:不仅用于展示,更适合做调试输出增强 ------尤其是当 __repr__ 已经不够表达结构信息时。
控制台自定义
最简单的方式是实现 __rich__ 方法。该方法不接收参数,返回一个 Rich 能渲染的对象,比如 Text、Table,甚至普通字符串。
如果返回字符串,它会按 Console Markup 解析。
示例:
python
class MyObject:
def __rich__(self) -> str:
return "[bold cyan]MyObject()"
如果打印 MyObject(),输出会以青色加粗 形式显示 MyObject()。
控制台渲染(高级)
__rich__ 只能返回单个渲染对象,如果你需要更复杂的输出结构,就要用 __rich_console__。
这个方法接收两个参数:
ConsoleConsoleOptions
返回的是一个可迭代对象(通常用 yield 实现)。
示例:
python
from dataclasses import dataclass
from rich.console import Console, ConsoleOptions, RenderResult
from rich.table import Table
@dataclass
class Student:
id: int
name: str
age: int
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
yield f"[b]Student:[/b] #{self.id}"
table = Table("Attribute", "Value")
table.add_row("name", self.name)
table.add_row("age", str(self.age))
yield table
如果打印 Student,会输出一段标题 + 表格结构。
底层渲染(Segment)
如果你想完全控制终端输出(比如逐字颜色控制),可以返回 Segment 对象。
Segment = 文本片段 + 可选样式。
示例:
python
class MyObject:
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
yield Segment("My", Style(color="magenta"))
yield Segment("Object", Style(color="green"))
yield Segment("()", Style(color="cyan"))
可视化测量(Measuring Renderables)
有时候 Rich 需要知道"一个对象渲染后占多少宽度",比如 Table 会用它来计算列宽。
如果是自定义对象,需要实现:
__rich_measure__
返回 Measurement(min, max),表示最小/最大宽度。
示例:
python
class ChessBoard:
def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
return Measurement(8, options.max_width)
附录
- Box(边框样式):https://rich.readthedocs.io/en/stable/appendix/box.html
- 标准颜色:https://rich.readthedocs.io/en/stable/appendix/colors.html
prompt_toolkit
如果说 Rich 解决的是"终端如何显示得更好看、更结构化",那么 prompt_toolkit 解决的就是另一个更底层的问题:如何让终端交互变得像一个现代编辑器一样可控、可扩展、甚至智能化。
在传统的命令行程序里,交互通常是非常朴素的------input() 读取一行文本,然后程序做出响应。这种模式简单但能力有限:没有自动补全、没有语法高亮、没有多行编辑能力,也很难处理复杂的交互逻辑。
prompt_toolkit 的出现,正是为了填补这一层能力空白。它提供了一整套构建交互式命令行应用的工具,包括:
- 强大的输入补全机制(类似 shell / IDE)
- 多行编辑与历史记录支持
- 可定制的 key bindings(按键行为)
- 语法高亮与动态提示
- 支持异步与复杂事件循环的输入系统
- 以及可以组合出"类 TUI 应用"的布局能力
更重要的是,它不是一个简单的"增强 input 的库",而是一个完整的终端交互框架。你可以用它做一个 REPL、一个 CLI 工具,甚至是一个轻量级的终端应用界面。
快速开始
使用 pip 安装:
bash
pip install prompt_toolkit
如果使用 Conda:
bash
conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit
prompt_toolkit 最初的设计目标是作为 readline 的替代品,用来增强命令行输入能力。
但随着项目逐渐成熟,它的能力被不断扩展,开发者发现:它已经具备构建**完整终端应用(full screen applications)**所需的全部基础组件。因此,像 pyvim 和 pymux 这样的全屏终端应用也开始基于它构建。

从架构上看,prompt_toolkit 的核心是一个布局引擎(layout engine),它支持:
- 水平 / 垂直分割布局
- 浮动窗口(float)
- 每个"窗口"都可以渲染一个自定义 UI 控件(user control)
这一套 API 表面上简单,但表达能力非常强,可以组合出复杂的终端界面。
当 prompt_toolkit 作为 readline 替代品使用时(例如只做简单输入),它会使用一个内置的默认布局,包括:
- 输入框(input buffer)
- 提示符(prompt)
- 自动补全浮窗(autocompletion float)
- 输入校验工具栏(默认隐藏)
而在全屏应用模式下,通常需要开发者自己设计整个布局结构。
此外,它还提供了一个非常灵活的按键绑定系统(key binding system),可以针对全屏应用进行深度定制。
下面是最基础的用法,通过 prompt() 获取用户输入,效果类似 input():
python
from prompt_toolkit import prompt
text = prompt("Give me some input: ")
print(f"You said: {text}")
为了更好理解 prompt_toolkit,建议按以下顺序学习:
首先学习文本输出(printing text),因为它会涉及"格式化文本(formatted text)",这是后续在任何地方使用颜色和样式的基础。
然后学习输入系统(asking for input),这是最核心的部分,几乎所有用法都会涉及,包括:
- 自动补全
- 语法高亮
- 按键绑定
接着可以学习 Dialogs(对话框),这一部分通常比较直观,也更容易上手。
最后再深入学习全屏应用开发以及相关的高级主题。
Printing(以及使用)格式化文本
prompt_toolkit 提供了一个 print_formatted_text() 函数,它在尽可能兼容内置 print 的同时,还支持颜色和文本样式。
在不同系统上的表现:
- 在 Linux 上:输出 VT100 转义序列
- 在 Windows 上:使用 Win32 API 或 VT100(取决于环境支持)
Printing plain text(打印普通文本)
python
from prompt_toolkit import print_formatted_text
print_formatted_text('Hello world')
你也可以直接替换内置 print:
python
from prompt_toolkit import print_formatted_text as print
print('Hello world')
Formatted text(格式化文本)
有多种方式可以显示颜色:
- 通过创建一个 HTML 对象
- 通过创建一个包含 ANSI 转义序列的 ANSI 对象
- 通过创建一个 (style, text) 元组列表
- 通过创建一个 (pygments.Token, text) 元组列表 ,并用
PygmentsTokens包装
上述四种对象中的任意一种实例,都被称为 "formatted text(格式化文本)" 。在 prompt_toolkit 的许多地方,不仅可以传入普通字符串(plain text),也可以传入这种格式化文本。
HTML
HTML 可以用于表示字符串中包含类似 HTML 的格式信息。它支持以下基础标签:
<b>粗体<i>斜体<u>下划线
python
from prompt_toolkit import print_formatted_text, HTML
print_formatted_text(HTML('<b>This is bold</b>'))
print_formatted_text(HTML('<i>This is italic</i>'))
print_formatted_text(HTML('<u>This is underlined</u>'))
此外,也可以使用标签来表示前景色:
python
# ANSI 调色板中的颜色
print_formatted_text(HTML('<ansired>This is red</ansired>'))
print_formatted_text(HTML('<ansigreen>This is green</ansigreen>'))
# 命名颜色(256 色或 true color,取决于输出环境)
print_formatted_text(HTML('<skyblue>This is sky blue</skyblue>'))
print_formatted_text(HTML('<seagreen>This is sea green</seagreen>'))
print_formatted_text(HTML('<violet>This is violet</violet>'))
前景色和背景色也可以通过 HTML 标签的 fg 和 bg 属性同时指定:
python
# ANSI 调色板颜色
print_formatted_text(HTML('<aaa fg="ansiwhite" bg="ansigreen">White on green</aaa>'))
在底层,所有 HTML 标签都会被映射到样式表中的类(classes),因此你可以为自定义标签分配样式:
python
from prompt_toolkit import print_formatted_text, HTML
from prompt_toolkit.styles import Style
style = Style.from_dict({
'aaa': '#ff0066',
'bbb': '#44ff00 italic',
})
print_formatted_text(HTML('<aaa>Hello</aaa> <bbb>world</bbb>!'), style=style)
ANSI
有些人更喜欢使用 VT100 ANSI 转义序列来生成输出。虽然这种方式原生只在 VT100 终端中支持,但 prompt_toolkit 可以解析这些序列,并将其转换为格式化文本实例,这意味着它们在 Windows 上同样可以工作。这一过程由 ANSI 类负责处理。
python
from prompt_toolkit import print_formatted_text, ANSI
print_formatted_text(ANSI('\x1b[31mhello \x1b[32mworld'))
需要注意的是,即使在 Linux 的 VT100 终端中,prompt_toolkit 最终生成的输出也不一定完全一致。根据颜色深度的不同,颜色可能会被映射为其他颜色,未知标签也可能被移除。
(style, text) 元组
在内部,HTML 和 ANSI 对象最终都会被转换为 (style, text) 元组列表。不过,你也可以使用 FormattedText 类手动构建这样的列表。这种方式稍显冗长,但也是表达格式化文本最强大的一种方式。
python
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText
text = FormattedText([
('#ff0066', 'Hello'),
('', ' '),
('#44ff00 italic', 'World'),
])
print_formatted_text(text)
类似 HTML 示例,你也可以使用类名,并将样式与文本分离到样式表中:
python
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.styles import Style
# 文本
text = FormattedText([
('class:aaa', 'Hello'),
('', ' '),
('class:bbb', 'World'),
])
# 样式表
style = Style.from_dict({
'aaa': '#ff0066',
'bbb': '#44ff00 italic',
})
print_formatted_text(text, style=style)
Pygments (Token, text) 元组
当你拥有一组 Pygments 的 (Token, text) 元组时,可以通过 PygmentsTokens 包装后进行输出:
python
from pygments.token import Token
from prompt_toolkit import print_formatted_text
from prompt_toolkit.formatted_text import PygmentsTokens
text = [
(Token.Keyword, 'print'),
(Token.Punctuation, '('),
(Token.Literal.String.Double, '"'),
(Token.Literal.String.Double, 'hello'),
(Token.Literal.String.Double, '"'),
(Token.Punctuation, ')'),
(Token.Text, '\n'),
]
print_formatted_text(PygmentsTokens(text))
同样,也可以直接输出 Pygments lexer 的结果:
python
import pygments
from pygments.token import Token
from pygments.lexers.python import PythonLexer
from prompt_toolkit.formatted_text import PygmentsTokens
from prompt_toolkit import print_formatted_text
# 输出 pygments lexer 的结果
tokens = list(pygments.lex('print("Hello")', lexer=PythonLexer()))
print_formatted_text(PygmentsTokens(tokens))
prompt_toolkit 内置了一套默认配色方案,其风格与 Pygments 类似。如果你想修改颜色,需要了解 Pygments token 到 class 名的映射关系:
pygments.Token
prompt_toolkit classname
Token.Keyword
Token.Punctuation
Token.Literal.String.Double
Token.Text
Token
"class:pygments.keyword"
"class:pygments.punctuation"
"class:pygments.literal.string.double"
"class:pygments.text"
"class:pygments"
类似 pygments.literal.string.double 这样的类名,实际上会被拆解为以下四个层级:
pygments
pygments.literal
pygments.literal.string
pygments.literal.string.double
最终样式是通过组合这四个 class 的样式计算得到的。因此,可以通过如下方式修改这些 token 的样式:
python
from prompt_toolkit.styles import Style
style = Style.from_dict({
'pygments.keyword': 'underline',
'pygments.literal.string': 'bg:#00ff00 #ffffff',
})
print_formatted_text(PygmentsTokens(tokens), style=style)
to_formatted_text
一个很有用的函数是 to_formatted_text()。它可以确保输入被转换为合法的格式化文本,同时还可以附加额外样式。
python
from prompt_toolkit.formatted_text import to_formatted_text, HTML
from prompt_toolkit import print_formatted_text
html = HTML('<aaa>Hello</aaa> <bbb>world</bbb>!')
text = to_formatted_text(html, style='class:my_html bg:#00ff00 italic')
print_formatted_text(text)
Asking for input (prompts)(输入提示 / prompts)
Hello world
下面的代码是最简单的示例,使用 prompt() 函数向用户请求输入,并返回输入文本,效果类似 (raw_)input:
python
from prompt_toolkit import prompt
text = prompt("Give me some input: ")
print(f"You said: {text}")

我们得到的是一个简单的 prompt,它支持类似 readline 的 Emacs 键位绑定,但除此之外没有特别之处。不过,prompt() 有很多可配置项,接下来会逐步介绍这些参数。
The PromptSession object(PromptSession 对象)
除了直接调用 prompt() 函数,还可以创建一个 PromptSession 实例,并通过调用其 prompt() 方法来获取输入。这相当于创建了一个输入会话。
python
from prompt_toolkit import PromptSession
# 创建 prompt 对象
session = PromptSession()
# 多次获取输入
text1 = session.prompt()
text2 = session.prompt()
这样做主要有两个优点:
- 多次调用
prompt()时,输入历史会被保留 PromptSession()和它的prompt()方法接受几乎相同的参数(例如高亮、补全等)。
如果你需要多次输入且参数基本一致,可以把这些参数传给PromptSession(),也可以在单次prompt()调用时覆盖它们
Syntax highlighting(语法高亮)
添加语法高亮非常简单,只需要提供一个 lexer。所有 Pygments 的 lexer 都可以使用,只需用 PygmentsLexer 包装。你也可以通过实现 Lexer 抽象基类来自定义 lexer。
python
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
text = prompt("Enter HTML: ", lexer=PygmentsLexer(HtmlLexer))
print(f"You said: {text}")

prompt_toolkit 默认已经包含了一个与 Pygments 类似的配色方案。如果你想使用其他 Pygments 风格,可以这样做:
python
from pygments.lexers.html import HtmlLexer
from pygments.styles import get_style_by_name
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.styles.pygments import style_from_pygments_cls
style = style_from_pygments_cls(get_style_by_name("monokai"))
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=style,
include_default_pygments_style=False
)
print(f"You said: {text}")
这里设置 include_default_pygments_style=False,是因为否则默认样式会与自定义样式合并,可能导致某些颜色与预期略有差异(尤其是自定义样式未覆盖的部分)。
Colors(颜色)
语法高亮的颜色由 Style 实例定义。默认使用一个中性的内置样式,但你可以传入任意自定义样式。
创建样式的一种简单方式是使用 from_dict():
python
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style
from prompt_toolkit.lexers import PygmentsLexer
our_style = Style.from_dict({
"pygments.comment": "#888888 bold",
"pygments.keyword": "#ff88ff bold",
})
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=our_style
)
样式字典与 Pygments 的样式字典非常相似,但有以下区别:
roman、sans、mono和border选项会被忽略- 增加了额外样式:
blink、noblink、reverse、noreverse - 颜色既可以用
#ff0000格式,也可以使用 ANSI 颜色名(对应终端的 16 色调色板)
Using a Pygments style(使用 Pygments 样式)
所有 Pygments 样式类都可以通过 style_from_pygments_cls() 使用。
例如使用 TangoStyle:
python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import style_from_pygments_cls
from prompt_toolkit.lexers import PygmentsLexer
from pygments.styles.tango import TangoStyle
from pygments.lexers.html import HtmlLexer
tango_style = style_from_pygments_cls(TangoStyle)
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=tango_style
)
创建自定义样式(在 Pygments 样式基础上扩展):
python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style, style_from_pygments_cls, merge_styles
from prompt_toolkit.lexers import PygmentsLexer
from pygments.styles.tango import TangoStyle
from pygments.lexers.html import HtmlLexer
our_style = merge_styles([
style_from_pygments_cls(TangoStyle),
Style.from_dict({
"pygments.comment": "#888888 bold",
"pygments.keyword": "#ff88ff bold",
})
])
text = prompt(
"Enter HTML: ",
lexer=PygmentsLexer(HtmlLexer),
style=our_style
)
你也可以为 prompt 本身添加颜色。为此,需要构造一个 formatted text。
一种方式是使用 (style, text) 元组列表,并通过 class 名引用样式:
python
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.styles import Style
style = Style.from_dict({
# 用户输入(默认文本)
"": "#ff0066",
# Prompt 各部分
"username": "#884444",
"at": "#00aa00",
"colon": "#0000aa",
"pound": "#00aa00",
"host": "#00ffff bg:#444400",
"path": "ansicyan underline",
})
message = [
("class:username", "john"),
("class:at", "@"),
("class:host", "localhost"),
("class:colon", ":"),
("class:path", "/user/john"),
("class:pound", "# "),
]
text = prompt(message, style=style)

message 可以是任意形式的 formatted text(如前文所述),也可以是一个返回 formatted text 的函数。
默认情况下,颜色使用 256 色调色板。如果你想使用 24 位真彩色,可以在 prompt() 中指定:
python
from prompt_toolkit.output import ColorDepth
text = prompt(message, style=style, color_depth=ColorDepth.TRUE_COLOR)
自动补全(Autocompletion)
可以通过传入 completer 参数来添加自动补全功能。这个参数应该是一个 Completer 抽象基类的实例。WordCompleter 是实现了该接口的一个示例补全器。
python
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
html_completer = WordCompleter(["<html>", "<body>", "<head>", "<title>"])
text = prompt("Enter HTML: ", completer=html_completer)
print(f"You said: {text}")
WordCompleter 是一个简单的补全器,它会使用给定的词列表来补全光标前的最后一个单词。

需要注意,在 prompt_toolkit 2.0 中,自动补全变成了同步执行。这意味着如果补全计算耗时较长,会阻塞事件循环和输入处理。
对于较重的补全逻辑,建议将补全器包装在 ThreadedCompleter 中,以便在后台线程运行。
嵌套补全(Nested completion)
有时你会遇到这样的命令行接口:补全内容依赖于之前输入的内容。例如路由器或交换机的 CLI。
这种情况下,简单的 WordCompleter 不够用,我们需要多层级的补全结构。NestedCompleter 可以解决这个问题:
python
from prompt_toolkit import prompt
from prompt_toolkit.completion import NestedCompleter
completer = NestedCompleter.from_nested_dict({
"show": {
"version": None,
"clock": None,
"ip": {
"interface": {"brief"}
}
},
"exit": None,
})
text = prompt("# ", completer=completer)
print(f"You said: {text}")
当字典中的值为 None 时,表示该位置不再有更深层的补全。
如果一个字典的所有值都是 None,也可以直接用 set 来替代。
自定义补全器(A custom completer)
对于更复杂的场景,可以自己实现一个补全器:
python
from prompt_toolkit import prompt
from prompt_toolkit.completion import Completer, Completion
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
yield Completion("completion", start_position=0)
text = prompt("> ", completer=MyCustomCompleter())
Completer 类需要实现一个生成器方法 get_completions(),该方法接收 Document,并生成 Completion 实例。
每个 Completion 包含:
- 要插入的文本
- 替换位置(start_position)
这个位置用于修改光标前的文本。例如:
- 可以把小写转成大写(用于大小写不敏感补全)
- 或者修正拼写错误(模糊匹配)
如果 start_position 是负数,表示向前删除对应数量的字符再替换。
单个补全项的样式(Styling individual completions)
每个补全项都可以提供自定义样式,这个样式会在补全菜单或工具栏中渲染时使用。可以通过为每个 Completion 实例传入 style 参数来实现。
python
from prompt_toolkit.completion import Completer, Completion
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
# 黑底黄字
yield Completion(
"completion1",
start_position=0,
style="bg:ansiyellow fg:ansiblack"
)
# 下划线
yield Completion(
"completion2",
start_position=0,
style="underline"
)
# 使用 class(在样式表中定义)
yield Completion(
"completion3",
start_position=0,
style="class:special-completion"
)
示例 colorful-prompts.py 展示了补全样式效果:

可以为 Completion 提供格式化文本作为显示内容(display),从而实现更灵活的展示:
python
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.formatted_text import HTML
class MyCustomCompleter(Completer):
def get_completions(self, document, complete_event):
yield Completion(
"completion1",
start_position=0,
display=HTML("<b>completion</b><ansired>1</ansired>"),
style="bg:ansiyellow"
)
模糊补全(Fuzzy completion)
如果某个可能的补全项是 "django_migrations",那么模糊补全允许你只输入 "djm"(这个字符串的一部分字符子集)就能匹配到它。
prompt_toolkit 自带了 FuzzyCompleter 和 FuzzyWordCompleter 类。这些类提供了实现这种"模糊补全"的能力。第一个可以接收任意 completer 实例并将其包装,使其变成一个模糊补全器;第二个的行为类似于将 WordCompleter 包装进一个 FuzzyCompleter。
自动补全可以在输入时自动触发,也可以在用户按下 Tab 键时触发。可以通过 complete_while_typing 选项进行配置:
python
text = prompt(
"Enter HTML: ",
completer=my_completer,
complete_while_typing=True
)
注意,这个设置与 enable_history_search 选项是不兼容的。原因是上下方向键的绑定会发生冲突。因此,请确保为此禁用历史搜索功能。
异步补全(Asynchronous completion)
当生成补全结果耗时较长时,最好将其放到后台线程中执行。这可以通过将 completer 包装为 ThreadedCompleter 来实现,也可以通过传入 complete_in_thread=True 参数来实现。
python
text = prompt("> ", completer=MyCustomCompleter(), complete_in_thread=True)
输入校验(Input validation)
一个 prompt 可以附加一个验证器(validator)。这是一段代码,用来检查用户输入是否有效。只有在输入符合要求时才会返回,否则会显示错误信息,并将光标移动到指定位置。
一个验证器需要实现 Validator 抽象基类。它只需要实现一个方法:validate,该方法接收一个 Document 作为输入,当验证失败时抛出 ValidationError。
python
from prompt_toolkit.validation import Validator, ValidationError
from prompt_toolkit import prompt
class NumberValidator(Validator):
def validate(self, document):
text = document.text
if text and not text.isdigit():
i = 0
# 获取第一个非数字字符的索引。
# 我们希望将光标移动到这里。
for i, c in enumerate(text):
if not c.isdigit():
break
raise ValidationError(
message="This input contains non-numeric characters",
cursor_position=i
)
number = int(prompt("Give a number: ", validator=NumberValidator()))
print(f"You said: {number}")

默认情况下,在用户输入的过程中会实时进行校验,但 prompt_toolkit 也支持在用户按下回车后再进行校验:
python
prompt(
"Give a number: ",
validator=NumberValidator(),
validate_while_typing=False
)
如果输入校验包含一些计算密集型(CPU-heavy)的代码,但你又不希望阻塞事件循环,建议将验证器包装在 ThreadedValidator 中。
使用函数创建验证器(Validator from a callable)
除了实现 Validator 抽象基类外,也可以从一个简单函数开始,通过 from_callable() 类方法创建验证器。这种方式更简单,且对大约 90% 的验证场景已经足够。
示例如下:
python
from prompt_toolkit.validation import Validator
from prompt_toolkit import prompt
def is_number(text):
return text.isdigit()
validator = Validator.from_callable(
is_number,
error_message="This input contains non-numeric characters",
move_cursor_to_end=True
)
number = int(prompt("Give a number: ", validator=validator))
print(f"You said: {number}")
这里我们定义了一个函数,接收字符串并返回布尔值来表示输入是否有效。from_callable() 会将其转换为一个 Validator 实例。需要注意的是,这种方式无法设置光标位置。
历史记录(History)
History 对象用于记录所有之前输入过的字符串,这样就可以通过上方向键查看历史输入。
推荐的方式是使用 PromptSession,它默认会在整个会话中使用一个 InMemoryHistory。
如下示例默认就带有历史功能:
python
from prompt_toolkit import PromptSession
session = PromptSession()
while True:
session.prompt()
如果希望将历史记录持久化到磁盘,可以使用 FileHistory 替代默认的 InMemoryHistory。这个历史对象可以传给 PromptSession 或 prompt() 函数:
python
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
session = PromptSession(history=FileHistory("~/.myhistory"))
while True:
session.prompt()
自动建议(Auto suggestion)
自动建议是一种类似 fish shell 的输入提示方式,用于向用户建议可能的补全内容。
通常情况下,输入内容会与历史记录进行比对,当存在以当前输入开头的历史记录时,会在当前输入后以灰色文本显示建议内容。按右箭头 → 或 c-e 可以插入该建议,按 alt-f 可以插入建议的第一个单词。
注意(Note)
当建议基于历史记录时,请确保在多次 prompt() 调用之间共享同一个 History 对象。使用 PromptSession 会自动帮你完成这一点。
示例(Example):
python
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
session = PromptSession()
while True:
text = session.prompt("> ", auto_suggest=AutoSuggestFromHistory())
print(f"You said: {text}")

自动建议不一定必须来自历史记录。任何实现了 AutoSuggest 抽象基类的对象都可以作为参数传入。
下面是**完整逐段翻译(不删减、不省略)**👇
添加底部工具栏(Adding a bottom toolbar)
添加一个底部工具栏非常简单,只需要向 prompt() 传入一个 bottom_toolbar 参数即可。这个参数可以是:
- 普通文本
- 格式化文本
- 或一个返回普通文本或格式化文本的可调用对象(函数)
当传入函数时,每次渲染 prompt 时都会调用它,因此底部工具栏可以用于显示动态信息。
当 prompt 返回时,工具栏总是会被清除。下面是一个示例,使用一个返回 HTML 对象的函数。默认情况下,工具栏使用反色(reversed)样式,因此这里设置的是背景色而不是前景色。
python
from prompt_toolkit import prompt
from prompt_toolkit.formatted_text import HTML
def bottom_toolbar():
return HTML('This is a <b><style bg="ansired">Toolbar</style></b>!')
text = prompt("> ", bottom_toolbar=bottom_toolbar)
print(f"You said: {text}")
.../_images/bottom-toolbar.png
类似地,也可以使用 (style, text) 元组列表:
python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
def bottom_toolbar():
return [("class:bottom-toolbar", " This is a toolbar. ")]
style = Style.from_dict({
"bottom-toolbar": "#ffffff bg:#333333",
})
text = prompt("> ", bottom_toolbar=bottom_toolbar, style=style)
print(f"You said: {text}")
默认的类名是 bottom-toolbar,它也会用于填充工具栏的背景。
添加右侧提示(Adding a right prompt)
prompt() 函数也原生支持右侧提示(right prompt)。熟悉 ZSH 的用户可能会把它理解为 RPROMPT 选项。
因此,类似添加底部工具栏,我们可以传入一个 rprompt 参数。这个参数可以是:
- 普通文本
- 格式化文本
- 或一个返回这些内容的可调用对象
python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
example_style = Style.from_dict({
"rprompt": "bg:#ff0066 #ffffff",
})
def get_rprompt():
return "<rprompt>"
answer = prompt("> ", rprompt=get_rprompt, style=example_style)

get_rprompt 函数可以返回任意格式化文本(例如 HTML)。同样,也可以直接向 prompt() 的 rprompt 参数传入文本,而不一定要使用函数。
Vi 输入模式(Vi input mode)
prompt_toolkit 同时支持 Emacs 和 Vi 两种按键绑定(类似 Readline)。
默认情况下,prompt() 使用 Emacs 绑定。这是因为在大多数操作系统中,Bash shell 默认也是使用 Emacs 绑定,更符合直觉。
如果需要使用 Vi 绑定,只需传入 vi_mode=True:
python
from prompt_toolkit import prompt
prompt("> ", vi_mode=True)
添加自定义按键绑定(Adding custom key bindings)
默认情况下,每个 prompt 已经内置了一套按键绑定,实现了常见的 Vi 或 Emacs 行为。我们可以通过向 prompt() 函数或 PromptSession 类传入一个 KeyBindings 实例,来扩展这些绑定(通过 key_bindings 参数)。
下面是一个示例:当按下 Control-T 时打印 'hello world'。
python
from prompt_toolkit import prompt
from prompt_toolkit.application import run_in_terminal
from prompt_toolkit.key_binding import KeyBindings
bindings = KeyBindings()
@bindings.add("c-t")
def _(event):
" 当按下 `c-t` 时输出 'hello'。 "
def print_hello():
print("hello world")
run_in_terminal(print_hello)
@bindings.add("c-x")
def _(event):
" 当按下 `c-x` 时退出。 "
event.app.exit()
text = prompt("> ", key_bindings=bindings)
print(f"You said: {text}")
注意,在第一个按键绑定中我们使用了 run_in_terminal()。这样可以确保 print 输出不会和 prompt 的显示混在一起。如果按键绑定本身不产生输出,则可以直接处理,而不需要嵌套函数。
根据条件启用按键绑定(Enable key bindings according to a condition)
通常,有些按键绑定需要根据某些条件启用或禁用。例如,Emacs 和 Vi 的按键绑定不会同时生效,但可以在运行时切换。
要根据条件启用按键绑定,需要传入一个 Filter(通常是 Condition 实例)。
(更多内容请参考 filters 文档。)
python
from prompt_toolkit import prompt
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
bindings = KeyBindings()
@Condition
def is_active():
" 仅在每分钟的后半段激活该按键绑定。 "
return datetime.datetime.now().second > 30
@bindings.add("c-t", filter=is_active)
def _(event):
# ...
pass
prompt("> ", key_bindings=bindings)
在 Emacs 和 Vi 模式之间动态切换(Dynamically switch between Emacs and Vi mode)
Application 对象有一个 editing_mode 属性。我们可以通过修改这个属性,在 EditingMode.VI 和 EditingMode.EMACS 之间切换按键绑定。
python
from prompt_toolkit import prompt
from prompt_toolkit.application.current import get_app
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding import KeyBindings
def run():
# 创建一组按键绑定
bindings = KeyBindings()
# 添加一个额外的按键绑定,用于切换模式
@bindings.add("f4")
def _(event):
" 在 Emacs 和 Vi 模式之间切换。 "
app = event.app
if app.editing_mode == EditingMode.VI:
app.editing_mode = EditingMode.EMACS
else:
app.editing_mode = EditingMode.VI
# 在底部添加一个工具栏,显示当前输入模式
def bottom_toolbar():
" 显示当前输入模式。 "
text = "Vi" if get_app().editing_mode == EditingMode.VI else "Emacs"
return [
("class:toolbar", " [F4] %s " % text)
]
prompt("> ", key_bindings=bindings, bottom_toolbar=bottom_toolbar)
run()
使用 control-space 触发补全(Using control-space for completion)
一种常见的快捷方式是使用 Control-Space 来打开自动补全菜单,而不是使用 Tab 键。可以通过如下按键绑定实现:
python
kb = KeyBindings()
@kb.add("c-space")
def _(event):
" 初始化自动补全,或选择下一个补全项。 "
buff = event.app.current_buffer
if buff.complete_state:
buff.complete_next()
else:
buff.start_completion(select_first=False)
其他 prompt 选项
多行输入(Multiline input)
读取多行输入只需要传入 multiline=True 参数即可。
python
from prompt_toolkit import prompt
prompt("> ", multiline=True)
这样做的一个副作用是:回车键现在会插入换行符,而不是提交输入。用户需要按 Meta+Enter(或先按 Escape 再按 Enter)来提交输入。
可以指定一个"续行提示"(continuation prompt)。通过向 prompt() 传入 prompt_continuation 参数实现,该参数是一个可调用对象。这个函数需要返回格式化文本,或 (style, text) 元组列表。返回文本的宽度不能超过给定宽度(提示区域的宽度由 prompt 决定)。
python
from prompt_toolkit import prompt
def prompt_continuation(width, line_number, is_soft_wrap):
return "." * width
# 或:return [("", "." * width)]
prompt(
"multiline input> ",
multiline=True,
prompt_continuation=prompt_continuation
)

设置默认值(Passing a default)
可以提供一个默认值:
python
from prompt_toolkit import prompt
import getpass
prompt("What is your name: ", default=f"{getpass.getuser()}")
鼠标支持(Mouse support)
支持有限的鼠标操作,包括:
- 光标定位
- 滚动(用于多行输入)
- 点击自动补全菜单
通过 mouse_support=True 启用:
python
from prompt_toolkit import prompt
prompt("What is your name: ", mouse_support=True)
换行方式(Line wrapping)
默认启用自动换行,这也是大多数用户熟悉的行为(与 GNU Readline 一致)。
如果关闭,则输入内容会横向滚动。
python
from prompt_toolkit import prompt
prompt("What is your name: ", wrap_lines=False)
密码输入(Password input)
当设置 is_password=True 时,输入内容会被星号(*)替代显示:
python
from prompt_toolkit import prompt
prompt("Enter password: ", is_password=True)
光标形状(Cursor shapes)
许多终端支持不同的光标形状,例如:方块(block)、竖线(beam)、下划线(underscore),以及是否闪烁。
可以在输入时指定光标样式,或者在 Vi 模式下根据不同模式动态变化。
python
from prompt_toolkit import prompt
from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig
prompt(">", cursor=CursorShape.BLOCK)
prompt(">", cursor=CursorShape.UNDERLINE)
prompt(">", cursor=CursorShape.BEAM)
prompt(">", cursor=CursorShape.BLINKING_BLOCK)
prompt(">", cursor=CursorShape.BLINKING_UNDERLINE)
prompt(">", cursor=CursorShape.BLINKING_BEAM)
prompt(">", cursor=ModalCursorShapeConfig())
添加边框(Adding a frame)
可以通过 show_frame=True 给输入框加一个边框。边框颜色可以通过样式中的 frame.border 控制:
python
from prompt_toolkit import prompt
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#884444",
}
)
answer = prompt("Say something > ", style=style, show_frame=True)
print(f"You said: {answer}")
也可以通过 filter 控制显示条件,例如只在输入时显示边框:
python
from prompt_toolkit import prompt
from prompt_toolkit.filters import is_done
answer = prompt("Say something > ", show_frame=~is_done)
print(f"You said: {answer}")

在 asyncio 应用中使用 prompt(Prompt in an asyncio application)
在 asyncio 应用中,绝不能阻塞事件循环。但 prompt() 是阻塞的,会冻结整个应用,因此不能在协程中直接调用。
解决方案是使用 prompt_async(),它返回一个 coroutine,可以使用 await:
python
from prompt_toolkit import PromptSession
from prompt_toolkit.patch_stdout import patch_stdout
async def my_coroutine():
session = PromptSession()
while True:
with patch_stdout():
result = await session.prompt_async("Say something: ")
print(f"You said: {result}")
patch_stdout() 是可选的,但推荐使用,因为其他协程可能会向 stdout 输出内容,它可以避免这些输出破坏 prompt 显示。
不显示 prompt,逐键读取 stdin(Reading keys from stdin, one key at a time, but without a prompt)
如果你只想逐个读取按键(而不显示 prompt),也可以做到:
python
import asyncio
from prompt_toolkit.input import create_input
from prompt_toolkit.keys import Keys
async def main() -> None:
done = asyncio.Event()
input = create_input()
def keys_ready():
for key_press in input.read_keys():
print(key_press)
if key_press.key == Keys.ControlC:
done.set()
with input.raw_mode():
with input.attach(keys_ready):
await done.wait()
if __name__ == "__main__":
asyncio.run(main())
这个示例会在每次按键时打印一个 KeyPress 对象。该方式是跨平台的,在 Windows 上也可以正常工作。
询问选择(Asking for a choice)
类似于 prompt() 函数用于文本输入,prompt_toolkit 也提供了 choice() 函数,用于从一组选项中进行选择:
python
from prompt_toolkit.shortcuts import choice
result = choice(
message="Please choose a dish:",
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
default="salad",
)
print(f"You have chosen: {result}")

选项着色(Coloring the options)
可以自定义颜色和样式。message 参数可以接受任意格式化文本(formatted text),而 options 中的标签(即每个选项的第二个参数)同样也可以是格式化文本。此外,我们还可以通过 from_dict() 函数传入一个 Style 实例:
python
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"input-selection": "fg:#ff0000",
"number": "fg:#884444 bold",
"selected-option": "underline",
}
)
result = choice(
message=HTML("<u>Please select a dish</u>:"),
options=[
("pizza", "Pizza with mushrooms"),
(
"salad",
HTML("<ansigreen>Salad</ansigreen> with <ansired>tomatoes</ansired>"),
),
("sushi", "Sushi"),
],
style=style,
)
print(f"You have chosen: {result}")

添加边框(Adding a frame)
choice() 函数支持 show_frame 参数。
当设置为 True 时,输入区域会显示在一个边框中。也可以传入 filter(例如 ~is_done),使边框只在输入阶段显示,提交后隐藏。
python
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.filters import is_done
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#884444",
"selected-option": "bold",
}
)
result = choice(
message="Please select a dish:",
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
style=style,
show_frame=~is_done,
)
print(f"You have chosen: {result}")

添加底部工具栏(Adding a bottom toolbar)
可以通过 bottom_toolbar 参数添加底部工具栏。该参数可以是:
- 普通文本
- 格式化文本
- 返回文本/格式化文本的函数
python
from prompt_toolkit.filters import is_done
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import choice
from prompt_toolkit.styles import Style
style = Style.from_dict(
{
"frame.border": "#ff4444",
"selected-option": "bold",
# (noreverse:因为默认工具栏样式使用 reverse)
"bottom-toolbar": "#ffffff bg:#333333 noreverse",
}
)
result = choice(
message=HTML("<u>Please select a dish</u>:"),
options=[
("pizza", "Pizza with mushrooms"),
("salad", "Salad with tomatoes"),
("sushi", "Sushi"),
],
style=style,
bottom_toolbar=HTML(
" Press <b>[Up]</b>/<b>[Down]</b> to select, <b>[Enter]</b> to accept."
),
show_frame=~is_done,
)
print(f"You have chosen: {result}")

对话框(Dialogs)
prompt_toolkit 提供了一套高级 API,用于显示对话框,类似 Whiptail 程序,但完全用 Python 实现。
消息框(Message box)
使用 message_dialog() 函数可以显示一个简单的消息框。例如:
python
from prompt_toolkit.shortcuts import message_dialog
message_dialog(
title='Example dialog window',
text='Do you want to continue?\nPress ENTER to quit.'
).run()

输入框(Input box)
input_dialog() 函数可以显示一个输入框,并返回用户输入的字符串。
python
from prompt_toolkit.shortcuts import input_dialog
text = input_dialog(
title='Input dialog example',
text='Please type your name:'
).run()
可以传入 password=True 参数,将其变为密码输入框。

是/否确认对话框(Yes/No confirmation dialog)
yes_no_dialog() 函数用于显示一个是/否确认框,返回布尔值(True 或 False)。
python
from prompt_toolkit.shortcuts import yes_no_dialog
result = yes_no_dialog(
title='Yes/No dialog example',
text='Do you want to confirm?'
).run()

按钮对话框(Button dialog)
button_dialog() 用于显示带按钮选项的对话框。按钮以元组列表形式提供,每个元组包含:
- 标签(label)
- 点击后返回值(return value)
python
from prompt_toolkit.shortcuts import button_dialog
result = button_dialog(
title='Button dialog example',
text='Do you want to confirm?',
buttons=[
('Yes', True),
('No', False),
('Maybe...', None)
],
).run()

单选列表对话框(Radio list dialog)
radiolist_dialog() 显示单选列表。值以元组列表提供,每个元组包含:
- 返回值(第一个元素)
- 显示值(第二个元素)
python
from prompt_toolkit.shortcuts import radiolist_dialog
result = radiolist_dialog(
title="RadioList dialog",
text="Which breakfast would you like ?",
values=[
("breakfast1", "Eggs and beacon"),
("breakfast2", "French breakfast"),
("breakfast3", "Equestrian breakfast")
]
).run()
多选列表对话框(Checkbox list dialog)
checkboxlist_dialog() 与单选列表类似,但允许选择多个值,因此返回的是一个数组。
python
from prompt_toolkit.shortcuts import checkboxlist_dialog
results_array = checkboxlist_dialog(
title="CheckboxList dialog",
text="What would you like in your breakfast ?",
values=[
("eggs", "Eggs"),
("bacon", "Bacon"),
("croissants", "20 Croissants"),
("daily", "The breakfast of the day")
]
).run()
对话框样式(Styling of dialogs)
所有对话框都可以传入自定义 Style 实例来覆盖默认样式。
此外,文本本身也可以通过 HTML 进行样式化。
python
from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.shortcuts import message_dialog
from prompt_toolkit.styles import Style
example_style = Style.from_dict({
'dialog': 'bg:#88ff88',
'dialog frame.label': 'bg:#ffffff #000000',
'dialog.body': 'bg:#000000 #00ff00',
'dialog shadow': 'bg:#00aa00',
})
message_dialog(
title=HTML('<style bg="blue" fg="white">Styled</style> '
'<style fg="ansired">dialog</style> window'),
text='Do you want to continue?\nPress ENTER to quit.',
style=example_style
).run()

结尾
很多人看到这篇 prompt_toolkit 的内容时,第一反应其实不是"这是一个Python库",而是"这不就是某些现代终端产品的实现方式吗"。
比如进度条,看起来就像 pip install 或各种 CLI 工具里那种动态加载条,而且配色、动画逻辑都很接近。再比如表格输出,很容易让人联想到类似 Claude Code 这类工具在终端里展示结构化数据的方式。甚至 Markdown 渲染、富文本样式、边框容器这些能力组合在一起,会让人产生一种错觉:终端已经"进化"成一个轻量 Web UI 运行环境了。
尤其是 choice() 这种交互组件,本质上就是一个"终端弹窗 UI",再配合底部 toolbar、frame、radio list、checkbox list,这些东西组合起来,确实很容易让人想到现代 AI coding assistant 的交互界面。比如你在 Claude Code 里看到的选择框、快捷提示栏,本质上就是同一类思想的实现:把结构化 UI 从 Web 挪进 terminal。
但关键点在于,这些并不是 Claude Code 独有的设计,也不是某个框架"突然变高级了",而是这一整套 terminal UI 技术早就存在了。prompt_toolkit、rich、click 这些库,本质上是在不同层次上把"终端变成可交互 UI"这件事做了封装。模型在训练数据里见过这些模式,所以当你让它"做一个类似 Claude Code 的 CLI",它确实能很自然地拼出一个看起来非常像的系统。
你说"那这些不都是 Python 写的吗?"------是的,但这恰恰不重要。Linux 下很多系统工具本身就是 Python 写的,而且它们的启动方式就是通过 shebang 指定解释器。也就是说,从运行模型的角度来看,语言只是实现手段,不是系统边界。
在 agent 场景里,真正的 IO 结构是网络请求 + 多轮模型调用 + 工具执行流,而不是 TS、JS、Python 之间的差异。只要能调用工具、能输出 UI、能处理状态机,这些语言都只是"载体"。
至于打包问题,其实也是一个工程取舍问题。像 PyInstaller 确实简单粗暴,会把解释器一起打进去,体积很大;但 Nuitka 这类方案可以把 Python 编译成本地二进制,虽然对复杂依赖不是 100% 兼容,但做一个 agent 或 CLI 工具通常已经足够用了。
所以回到最初那个感觉:
"这不就是把 Web UI 塞进终端了吗?"
某种程度上,是的。区别只是------它不是 HTML/CSS/JS,而是另一套"终端 UI DSL"。而 prompt_toolkit 做的事情,就是把这套 DSL 变得足够工程化,让你真的可以在 CLI 里搭一个像应用一样完整的交互系统。