news 2026/10/15 6:27:10

基于 Python、Textual 与 SQLite 构建联系人管理应用 RP Contacts:安装、运行与源码级剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 Python、Textual 与 SQLite 构建联系人管理应用 RP Contacts:安装、运行与源码级剖析
  • 示例工程
  • 教程

【免费下载链接】materials

Bonus materials, exercises, and example projects for our Python tutorials

项目地址:https://gitcode.com/gh_mirrors/ma/materials
点击查看免费下载

导读:本文围绕仓库 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 给出的安装流程分两步,核心是建立隔离的虚拟环境并安装依赖:

  1. 创建 Python 虚拟环境:
$ python -m venv ./venv $ source venv/bin/activate (venv) $
  1. 安装依赖:
(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)

流程如下:

  1. @on(Button.Pressed, "#add")监听 Add 按钮的按压事件;
  2. push_screen(InputDialog(), check_contact)把输入对话框压入屏幕栈,并注册回调check_contact;
  3. 用户在InputDialog中填写信息并点击 Ok 后,对话框通过dismiss((name, phone, email))携带元组返回,触发check_contact;
  4. 回调内先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-dialoggrid-size: 2; grid-rows: 1fr 3; width: 60; height: 11; border: solid red; background: $surface;确认对话框为 2 列网格,红色边框,宽度 60 字符
#input-dialoggrid-size: 3 5; width: 50; height: 20; border: solid green;输入对话框为 3 列 5 行网格,绿色边框
#titlecolumn-span: 3; color: green; text-style: bold;对话框标题横跨 3 列并加粗着色
.contacts-listwidth: 3fr; border: solid green;数据表占据 3 份弹性宽度,绿色边框
.buttons-panelalign: center top; width: auto; border: solid red;按钮面板顶部居中对齐,红色边框
.labelcontent-align: right middle;输入标签右对齐、垂直居中
.inputcolumn-span: 2;输入框横跨 2 列
.separatorheight: 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

项目地址:https://gitcode.com/gh_mirrors/ma/materials
点击查看免费下载
上一篇:Harvey LAB测试体系:离线测试与live测试策略
下一篇:Readest S3 兼容同步的 SigV4 线路编码缺陷修复:Qiniu Kodo 场景下的 RFC 3986 兼容实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 6:27:00

Chromium IPC ChannelProxy源码解析:线程模型与生命周期

直接拿IPC::Channel往业务代码里塞,往往跑不过一个版本周期就会遇到"偶发崩溃"或者"消息不见了"的诡异问题。这不是 Channel 本身不行,而是它的线程模型太容易用错。Chromium 的 base 库把IPC::ChannelProxy放在 Channel 之上&#…

作者头像 李华
网站建设 2026/10/15 6:26:52

softmax回归从原理到实践:多分类模型的核心与工程陷阱

接触分类任务的朋友应该都有体会:二分类场景下,逻辑回归是个默认选项,sigmoid一压,输出一个概率,阈值一卡就完事。但当标签不是一个“是/否”,而是三个甚至几十个离散类别时,很多人的第一反应是…

作者头像 李华
网站建设 2026/10/15 6:26:44

机器人PLC组信号GO/GI配置:地址映射与位号计算避坑指南

简介:组合信号(GO/GI)应用说明是一份面向ABB机器人工程技术人员与自动化集成工程师的PDF文档,重在解决多路数字量输入输出与PLC通信时常见的程序冗长、状态误判问题。文档从组合信号的定义讲起,指出组输出和组输入可将…

作者头像 李华
网站建设 2026/10/15 6:26:19

PDF和DOCX文档处理库选型指南:从开源到商业的实用笔记

做后端开发这些年,凡是碰到电子合同、发票归档、月度报表导出这类功能,几乎都绕不开两个格式:PDF和DOCX。每次评审技术方案,只要有人抛出“选哪个库”这个问题,我都会多问几句,因为这事表面看像是查一下文档…

作者头像 李华
网站建设 2026/10/15 6:25:29

PVSYST光伏系统设计全流程教程:从安装到发电量模拟

简介:面向光伏系统设计初学者与工程技术人员的 PVsyst 入门课件,系统地介绍了 PVsyst 光伏设计辅助软件的安装要求、主界面布局,以及并网光伏系统从初步设计到工程设计的完整流程。内容涵盖地理位置和气象参数选择、装机容量与组件面积设置、…

作者头像 李华
网站建设 2026/10/15 6:25:29

应用与实践:数学学习第五天的关键训练

“MATH Day 05 Applications & Practice”,这三个词拆开来看,是数学学习里最容易被低估的一组关键词组合。很多学习计划里都有“每日数学”的概念,前四天通常用来温习概念、公式和基础运算,到了第五天,任务会突然从…

作者头像 李华