- 示例工程
- 教程
【免费下载链接】materials
Bonus materials, exercises, and example projects for our Python tutorials
导读:本文围绕仓库 contact-book-python-textual 中的第五步源码(source_code_step_5)展开,完整讲解 RP Contacts 联系人应用的安装、运行方式,并结合源码深入剖析其 Textual 界面架构、SQLite 持久化层与样式系统。读完本文,你将掌握如何从零运行一个终端 GUI 联系人应用,并理解 Textual 中 App、Screen、DataTable、对话框与事件绑定的实际组合方式。
项目概述:RP Contacts 是什么
RP Contacts 是一个用 Python 与 Textual 构建的联系人管理应用,属于 Real Python 教程Build a Contact Book App With Python, Textual, and SQLite的配套代码。它以纯终端界面(TUI)呈现,不依赖浏览器或桌面窗口,就能完成联系人的浏览、新增等日常操作,并用 SQLite 数据库实现持久化存储。
当前讲解的source_code_step_5版本已经具备完整可运行的主体功能:界面布局、联系人列表展示、新增联系人、退出确认对话框与暗色模式切换。其完整目录结构如下:
contact-book-python-textual/source_code_step_5/ ├── README.md ├── requirements.txt └── rpcontacts/ ├── __init__.py # 版本信息 __version__ = "0.1.0" ├── __main__.py # 程序入口,组装 App 与 Database ├── database.py # SQLite 数据访问层 ├── rpcontacts.tcss # Textual 样式表 └── tui.py # 应用主体:ContactsApp 与对话框 Screen其中requirements.txt仅声明一个依赖:textual==0.75.1,应用的数据层完全依赖 Python 标准库中的sqlite3与pathlib,无需额外安装数据库服务。
环境准备与安装
安装前请确保本机已具备 Python 3 环境。官方 README 给出的安装流程分两步,核心是建立隔离的虚拟环境并安装依赖:
- 创建 Python 虚拟环境:
$ python -m venv ./venv $ source venv/bin/activate (venv) $- 安装依赖:
(venv) $ python -m pip install -r requirements.txt依赖安装完成后,textual及其依赖包会被装入虚拟环境。值得注意的是,本项目没有声明独立的版本管理工具(如 Poetry、uv),因此直接使用pip即可,适合作为学习 Textual 的入门样例。
运行项目
运行同样分为两步:先以可编辑模式(-e)安装当前包,使rpcontacts命令可直接调用,再执行命令启动应用:
(venv) $ python -m pip install -e . (venv) $ rpcontacts应用启动后,终端将进入 RP Contacts 的 Textual 界面。-e .之所以必要,是因为rpcontacts需要被注册为可执行命令;如果想跳过该步骤,也可以直接运行模块入口:
(venv) $ python -m rpcontacts两者最终都会走到 rpcontacts/main.py:
from rpcontacts.database import Database from rpcontacts.tui import ContactsApp def main(): app = ContactsApp(db=Database()) app.run() if __name__ == "__main__": main()这段入口代码清晰揭示了应用的组装方式:ContactsApp接收一个Database实例作为依赖,随后调用App.run()进入 Textual 事件循环。数据库与界面解耦,便于后续单独测试或替换数据层实现。
应用架构:界面层与数据层分离
从源码结构看,RP Contacts 采用了清晰的分层设计:
- 界面层(TUI):rpcontacts/tui.py 中定义
ContactsApp(继承textual.app.App),以及两个对话框QuestionDialog、InputDialog(均继承textual.screen.Screen); - 数据层(Database):rpcontacts/database.py 封装全部 SQLite 读写操作,界面层不直接书写 SQL;
- 样式层(CSS):rpcontacts/rpcontacts.tcss 通过
CSS_PATH = "rpcontacts.tcss"被 App 自动加载。
数据层:基于 SQLite 的 Database 类
数据库模块 定义了默认数据库路径:
DATABASE_PATH = pathlib.Path().home() / "contacts.db"即数据库文件默认生成在用户主目录下,名为contacts.db。Database.__init__接受可选的db_path参数(默认指向上述路径),构造时完成sqlite3.connect、创建游标并自动建表:
def _create_table(self): query = """ CREATE TABLE IF NOT EXISTS contacts( id INTEGER PRIMARY KEY, name TEXT, phone TEXT, email TEXT ); """ self._run_query(query)contacts表包含id(自增主键)、name、phone、email四个字段。所有写操作统一经_run_query提交:
def _run_query(self, query, *query_args): result = self.cursor.execute(query, [*query_args]) self.db.commit() return result_run_query使用参数化查询(?占位符)替代字符串拼接,从源头规避 SQL 注入风险。围绕它封装了五个业务方法,覆盖联系人的完整生命周期:
| 方法 | 对应 SQL | 用途 |
|---|---|---|
get_all_contacts() | SELECT * FROM contacts; | 读取全部联系人,供启动时填充列表 |
get_last_contact() | SELECT * FROM contacts ORDER BY id DESC LIMIT 1; | 获取最新插入的联系人,用于新增后即时刷新界面 |
add_contact(contact) | INSERT INTO contacts VALUES (NULL, ?, ?, ?); | 插入新联系人,NULL让id自增 |
delete_contact(id) | DELETE FROM contacts WHERE id=(?); | 按主键删除 |
clear_all_contacts() | DELETE FROM contacts; | 清空全部联系人 |
界面层:ContactsApp 与按键绑定
tui.py 中的ContactsApp通过BINDINGS声明全部快捷键:
BINDINGS = [ ("m", "toggle_dark", "Toggle dark mode"), ("a", "add", "Add"), ("d", "delete", "Delete"), ("c", "clear_all", "Clear All"), ("q", "request_quit", "Quit"), ]每个绑定元组依次为「按键、动作名、帮助提示」。Textual 会把动作名自动映射到类中的action_动作名方法;按下按键时,对应动作执行,同时该按键说明会显示在界面底部的Footer中。第五步源码实现了action_toggle_dark、action_request_quit、action_add三个动作;其中d(delete)与c(clear_all)的绑定已声明,对应的action_delete、action_clear_all方法则在后续步骤(source_code_step_6/rpcontacts/tui.py 及完整版 source_code/rpcontacts/tui.py)中补全,最终形成完整的增删清空闭环。
快捷键速查表如下:
| 按键 | 动作 | 说明 |
|---|---|---|
m | 切换暗色模式 | 反转self.dark,实现明暗主题切换 |
a | 新增联系人 | 弹出InputDialog输入姓名、电话、邮箱 |
d | 删除联系人 | 弹出确认对话框(完整版实现) |
c | 清空所有联系人 | 二次确认后清空(完整版实现) |
q | 退出应用 | 弹出确认对话框,确认后退出 |
主界面布局:DataTable 与按钮面板
compose()方法是 Textual 声明式构建界面的核心,它返回一组待挂载的组件:
def compose(self): yield Header() contacts_list = DataTable(classes="contacts-list") contacts_list.focus() contacts_list.add_columns("Name", "Phone", "Email") contacts_list.cursor_type = "row" contacts_list.zebra_stripes = True add_button = Button("Add", variant="success", id="add") add_button.focus() buttons_panel = Vertical( add_button, Button("Delete", variant="warning", id="delete"), Static(classes="separator"), Button("Clear All", variant="error", id="clear"), classes="buttons-panel", ) yield Horizontal(contacts_list, buttons_panel) yield Footer()关键设计点:
DataTable以三列(Name、Phone、Email)展示联系人,cursor_type = "row"让光标按整行移动,zebra_stripes = True启用斑马条纹便于阅读;- 三个按钮按视觉语义选用不同
variant:Add 用success(绿)、Delete 用warning(黄)、Clear All 用error(红); Vertical/Horizontal容器负责把数据表与按钮面板组合成左右布局,Static(classes="separator")作为弹性占位撑开按钮间距;- 每个带
id的组件都可在后续通过query_one("#id", 类型)精准查找。
on_mount在组件树挂载后触发,此时设置窗口标题并调用_load_contacts()从数据库回填数据:
def _load_contacts(self): contacts_list = self.query_one(DataTable) for contact_data in self.db.get_all_contacts(): id, *contact = contact_data contacts_list.add_row(*contact, key=id)这里用了解构赋值id, *contact = contact_data:把(id, name, phone, email)元组拆分后,将业务字段传给add_row,同时把id作为行的key存入 DataTable——后续删除操作正是借助这个key反查主键,实现界面行与数据库记录的对应。
新增联系人的完整调用链
action_add展示了 Textual 的弹窗(push_screen)与回调机制:
@on(Button.Pressed, "#add") def action_add(self): def check_contact(contact_data): if contact_data: self.db.add_contact(contact_data) id, *contact = self.db.get_last_contact() self.query_one(DataTable).add_row(*contact, key=id) self.push_screen(InputDialog(), check_contact)流程如下:
@on(Button.Pressed, "#add")监听 Add 按钮的按压事件;push_screen(InputDialog(), check_contact)把输入对话框压入屏幕栈,并注册回调check_contact;- 用户在
InputDialog中填写信息并点击 Ok 后,对话框通过dismiss((name, phone, email))携带元组返回,触发check_contact; - 回调内先
add_contact落库,再查询最新记录并add_row刷新界面——保证数据库与界面始终同步。
对话框:Screen 子类实现确认与输入
项目中两个对话框均以Screen子类实现,通过Grid布局:
QuestionDialog(确认对话框)接收任意消息文本,以 Yes/No 两个按钮收集用户决定,并依据被点击按钮的id调用dismiss(True)或dismiss(False)返回值:
def on_button_pressed(self, event): if event.button.id == "yes": self.dismiss(True) else: self.dismiss(False)退出应用即复用该对话框:push_screen(QuestionDialog("Do you want to quit?"), check_answer),用户确认后才真正self.exit()。
InputDialog(输入对话框)用三个Input控件分别收集 Name、Phone、Email,点击 Ok 时把三个输入值打包为元组返回,点击 Cancel 则返回空元组:
def on_button_pressed(self, event): if event.button.id == "ok": name = self.query_one("#name", Input).value phone = self.query_one("#phone", Input).value email = self.query_one("#email", Input).value self.dismiss((name, phone, email)) else: self.dismiss(())注意dismiss(())返回空元组的写法:action_add的回调中if contact_data:恰好用「元组是否为空」来判断用户是否真正提交,因此取消操作不会产生任何数据变更——这是 Textual 回调场景中简洁而巧妙的约定。
样式系统:rpcontacts.tcss 关键参数
Textual 使用类 CSS 语法控制终端布局。样式文件 中值得注意的声明包括:
| 选择器 | 关键声明 | 效果 |
|---|---|---|
#question-dialog | grid-size: 2; grid-rows: 1fr 3; width: 60; height: 11; border: solid red; background: $surface; | 确认对话框为 2 列网格,红色边框,宽度 60 字符 |
#input-dialog | grid-size: 3 5; width: 50; height: 20; border: solid green; | 输入对话框为 3 列 5 行网格,绿色边框 |
#title | column-span: 3; color: green; text-style: bold; | 对话框标题横跨 3 列并加粗着色 |
.contacts-list | width: 3fr; border: solid green; | 数据表占据 3 份弹性宽度,绿色边框 |
.buttons-panel | align: center top; width: auto; border: solid red; | 按钮面板顶部居中对齐,红色边框 |
.label | content-align: right middle; | 输入标签右对齐、垂直居中 |
.input | column-span: 2; | 输入框横跨 2 列 |
.separator | height: 1fr; | 弹性占位,撑开按钮间距 |
由于CSS_PATH指向与tui.py同目录的rpcontacts.tcss,开发者只需在 App 类中声明路径即可自动加载,无需手工注入样式。
数据持久化与运行注意
- 数据库位置:首次启动后,用户主目录下会生成
contacts.db;再次启动时CREATE TABLE IF NOT EXISTS保证表结构幂等创建,已存联系人会自动回填到界面。 - 依赖版本:项目锁定
textual==0.75.1(见 requirements.txt),建议在虚拟环境中按此版本安装,避免因 Textual API 演进导致示例代码不兼容。 - 功能演进:本步骤(step_5)已具备浏览、新增、主题切换与退出确认;删除单条、清空全部这两个操作在 source_code_step_6 与完整版 source_code 中实现,可对照阅读
action_delete与action_clear_all如何借助行key与确认对话框安全完成数据变更。
许可与来源
本项目配套说明由 Real Python 提供,按 MIT 许可证分发,许可文本参见仓库根目录的 LICENSE。本文所有结论均以 contact-book-python-textual 目录下的源码与 README 为依据,读者可对照源码逐步验证安装、运行与各功能调用链。
- 示例工程
- 教程
【免费下载链接】materials
Bonus materials, exercises, and example projects for our Python tutorials
相关推荐
用 Python、Textual 与 SQLite 构建 RP Contacts 终端联系人管理应用
用 Python、Textual 与 SQLite 构建 RP Contacts 终端联系人管理应用 本篇技术指南以仓库中 contact book pytho
示例工程教程用 Python 与 Textual 构建 RP Contacts 联系人簿 TUI:安装运行与界面骨架源码解析
用 Python 与 Textual 构建 RP Contacts 联系人簿 TUI:安装运行与界面骨架源码解析 本篇技术指南以 contact book py
示例工程教程RP Contacts 起步篇:用 Python 与 Textual 构建终端通讯录应用(source_code_step_1 安装运行与骨架剖析)
RP Contacts 起步篇:用 Python 与 Textual 构建终端通讯录应用(source_code_step_1 安装运行与骨架剖析) RP Co
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考