ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

五个值得认真试用的Python新库:从DuckDB到FastHTML

五个值得认真试用的Python新库:从DuckDB到FastHTML

1. 先说说我为什么盯着“新库”看

打开 PyPI 的热门榜单,你会发现一个现象:真正能让我从 pandas 手里跳出去的库,绝大多数是最近一两年才活跃起来的。今天想聊的不是那些刷 star 的玩具项目,而是五个已经能放进生产环境、值得认真花一个下午去试的 Python 新库:DuckDB、Polars、NiceGUI、Textual、FastHTML。它们分别覆盖数据查询、数据处理、Web 界面、终端应用和 Web 开发这几条主线。无论你是做数据分析、写爬虫、搞量化回测,还是偶尔需要给团队搭一个内部小工具,都有对应的一两个值得马上试试。

“新库”这个词很容易让人误会,不是说刚发布几天才叫新。我这里的标准是:它已经过了最危险的验证期,API 基本稳定,社区活跃度处于上升期,但还没有烂大街。pandas 当然好,可它太重了,很多场景用不上那么重的招式。这五个库的共同特点是小而准,安装一条命令,解决一个具体痛点,不绑架你的项目结构。用它们就像换了一把顺手的新螺丝刀,工具还是工具,但干活的手感完全不同。

1.1 这批新库的共同特征

我观察下来,值得关注的新库通常有三个共性。第一,针对某个老问题的“默认姿势”提出了另一种解法,比如 duckdb 直接把手伸向“数据分析已经越来越像 SQL”这个事实;第二,性能上不是挤牙膏式提升,而是用一个不同的实现方式带来数量级差异,Polars 就属于这一类;第三,大大降低了某个原本需要跨技术栈才能完成的事情的门槛,NiceGUI 和 FastHTML 都能让纯 Python 开发者完全不碰前端。

还有一个很关键的信号:不少新库都在试图减少“环境摩擦”。装一个库就能跑通,不需要额外启动服务,不需要配置一堆环境变量。这个体验对新手很重要,对老手更是加分项。如果你平时已经受够了“为了一个简单操作装三个框架”的工作流,那这五个库中的每一个都会让你有“这还差不多”的感觉。

1.2 我判断库值不值得用的三个硬指标

在深入聊之前,我先给你一套我自己用来筛选新库的指标,后面每个库我都会用这套标准去对照。第一,维护活跃度:看 GitHub 的 release 频率和最近一次提交时间,三个月不动的直接划走,哪怕它 star 再多,因为 star 只能说明话题度,说明不了可持续性。第二,API 稳定性:看它有没有发布 1.x 版本,或者版本号虽然没到 1.0,但 changelog 里是不是经常搞破坏性变更。第三,生态衔接能力:能不能跟 pandas、FastAPI、Jupyter 这些主流生态轻松互操作,不能融入生态的工具再漂亮也容易被孤立。

这套标准帮我省了很多时间。很多新库刚出来时确实惊艳,但写着写着作者就消失了,或者隔几天换一个 API 风格。下面这五个,是经过我实际项目验证、踩过坑之后留下的名单,不是那种“看得起用不起”的类型。

2. 第一个库:DuckDB —— 自带 SQL 引擎的分析利器

2.1 DuckDB 解决了什么问题

先说你最可能遇到的场景:手里放着一个几百 MB 甚至几个 GB 的 CSV 或者 Parquet 文件,你想快速看一下数据长什么样,按某个字段分分组、算一算均值。用 pandas 的话,read_csv 那一步就已经让人心慌,内存占用直接拉满,机器风扇呜呜转。现在有了 DuckDB,你可以把它当成一个跑在 Python 进程里的 SQLite,但它不是为事务设计的,而是为分析查询设计的。

DuckDB 的核心优势是“嵌入式 + SQL + 直接读文件”。不用装数据库服务,不用建表导数据,一条 SQL 就能跨文件查询。它内部使用了向量化执行引擎,列式存储,所以在做聚合这类分析型计算时非常快。我实测过读取一个 3GB 的 CSV 做 group by 聚合,pandas 花了大概 40 秒,内存占用接近 8GB;DuckDB 直接在文件上跑同样的查询,只花了几秒,内存稳定在几百 MB 的水平。这种差距不是优化出来的,是架构决定的。

2.2 三条命令跑通查询

安装很简单,pip install duckdb 就行。核心用法是 duckdb.sql() 直接写 SQL。最爽的一点是,它可以把 pandas DataFrame 当作表来查,不需要导来导去。

import duckdb import pandas as pd df = pd.DataFrame({"department": ["技术", "市场", "技术"], "salary": [20000, 12000, 18000]}) result = duckdb.sql( """ SELECT department, COUNT(*) AS cnt, AVG(salary) AS avg_salary FROM df GROUP BY department ORDER BY avg_salary DESC """ ).df() print(result)

这里的关键是.df()把查询结果转回 pandas DataFrame,方便后续处理。如果数据在 CSV 文件里,也直接一行搞定:

duckdb.sql("SELECT * FROM read_csv_auto('large_file.csv') WHERE age > 30 LIMIT 10")

read_csv_auto 会自动推断类型,遇到不规则的 CSV 也能尽量识别。配合 Parquet 文件时优势更明显,DuckDB 原生支持谓词下推,也就是说只读取你需要的列和行,而不是把整个文件解压到内存。

2.3 实际使用中的几个坑

第一,DuckDB 不是数据库服务,别拿它当 PostgreSQL 用。它不支持高并发写入,同一时刻只允许一个进程写文件,多路径持久化也不是它的菜。简单来说,它是分析工具,不是业务系统存储。第二,SQL 的语法和 MySQL 有点差异,例如字符串拼接用 ||,日期函数的名字也略有不同。遇到不熟悉的地方,先翻官方文档而不是硬套迁移过来的 SQL。第三,读 Excel 文件需要额外的插件,我第一次用时以为直接支持,结果调了老半天。你可以装 duckdb 的扩展: excel 插件,但没必要的话还是先转 CSV 更省心。

还有一个容易被忽略的点:DuckDB 的版本升级比较快,如果你在项目里用了它,建议把版本号固化在依赖里,别用 latest。我遇到过升级后某些类型的隐式转换策略变了,同样的 SQL 结果却不一样。这类问题排查起来很费时间,不如一开始就锁版本。

3. 第二个库:Polars —— 比 pandas 快出一个数量级的 DataFrame

3.1 Polars 和 pandas 到底差在哪

pandas 基本上是 Python 数据分析的默认语言了,但它的设计起点是单线程、基于 NumPy 的列式内存结构,处理千万级以上的数据时会很吃力。Polars 用 Rust 重写,底层是 Apache Arrow 列式内存格式,天然支持多线程并行,CPU 利用率比 pandas 高很多。而且它提供了两套 API:立即执行的 eager 模式和延迟优化的 lazy 模式。lazy 模式最吸引人,因为它会把你的多个操作组合成一个查询计划,自动做优化,比如下推过滤、剪枝不必要的列,最后才一次性计算。

我最早被 Polars 打动是在做特征工程的时候。原本 pandas 处理一亿行数据统计分组特征要跑三四分钟,换成 Polars 的 lazy API 之后,同样逻辑只需要十几秒。这种差距不是小修小补,而是能让你在交互式分析里多试几个特征组合,对做量化策略和推荐特征的人来说,体感上完全是两个世界。

3.2 lazy API 的正确玩法

用 Polars 时最容易犯的错误是没搞清楚 lazy 和 eager 的区别。你写 df.lazy() 之后,所有操作都只是记录下来,什么也没算;直到调用 collect() 才真正触发计算。如果中间忘了 collect,拿到的就是一个未计算的 LazyFrame,打印出来也只是一堆查询步骤,不是数据。

import polars as pl df = pl.read_csv("orders.csv") result = ( df.lazy() .filter(pl.col("status") == "completed") .group_by("customer_id") .agg(pl.col("amount").sum().alias("total")) .sort("total", descending=True) .head(10) .collect() )

这段代码的含义是:先过滤已完成订单,再按客户分组求和,排序后取前 10 名,最后才真正执行。如果你用 pandas 写同样逻辑,每一步都会立即产生一个中间副本,内存占用和计算量都上来了。Polars 的链式调用看起来很数学,但其实就是“把操作串起来,等到最后一刻才跑”。

另外,Polars 的 group_by 返回的是新 DataFrame,不会修改原来的文件。它的表达式 API 也比 pandas 更规整,agg 里可以同时算 sum、mean、count,组合起来非常灵活。

3.3 新手最容易翻车的三个地方

第一个是类型问题。pandas 里 int 列遇到缺失值会自动变 float,Polars 直接就是 null,语义更严谨。这本来是好事,但如果你习惯了 pandas 的“自动处理”,在 Polars 里就要小心:比如你往一列整数里写 None,数据框可能直接报错或者需要你显式标注类型。

第二个是和 pandas 互操作时的转换损耗。Polars 提供了 pl.from_pandas() 和 df.to_pandas(),但转换本身需要时间,特别是字符串类型,Arrow 和 Python object 之间的转换开销不小。最理想的用法是数据从 CSV、Parquet 读进来到最后输出都用 Polars,只在最外层需要可视化或写回旧代码时才转回 pandas。

第三个是 group_by 的聚合语法和 pandas 的范思维差异较大。在 pandas 里你写 df.groupby('col').agg({'x': 'sum'}),Polars 里则变成 df.group_by('col').agg(pl.col('x').sum()),用别名就用 pl.col('x').sum().alias('x_sum')。不少人第一回合就把 pl.col 和字符串混用,结果报错一脸懵。我建议刚开始时把 Polars 的表达式当成一种“列函数对象”,不要总想着 pandas 的任何写法可以平移过来。

4. 第三个库:NiceGUI —— 纯 Python 写内部工具界面

4.1 适合什么场景

你是不是也有过这种需求:手头写了一个 Python 脚本,能处理 Excel、能调用模型、能读数据库,但每次给别人用时都要命令行传参数,或者自己临时改代码改路径。你说给它配个 Web 界面吧,又不想为这点小需求去搭 React + Vue + 后端 API 三件套。NiceGUI 就是来治这个毛病的。

它基于 FastAPI 和一个前端 Vue 组件库,但你完全不用写 JavaScript,也不用写 HTML 模板。你在 Python 代码里定义一个按钮、一个输入框、一个表格,它就直接变成浏览器里能交互的页面。整体开发体验非常接近 Streamlit,但比 Streamlit 更接近“正常 Web 应用”,因为它的组件可以自由组合,支持事件回调、数据绑定、自定义布局。我自己常用它来给团队做内部数据看板、模型推理演示工具、还有运维脚本的简单控制台,平均两三个小时就能出一个能用的页面。

4.2 最小实例与核心概念

看一个最简单的例子。写一个表单,输入名字,点按钮后页面显示问候语。

from nicegui import ui def say_hello(): result.set_text(f"你好,{name_input.value}!") name_input = ui.input(placeholder="请输入名字") ui.button("打招呼", on_click=say_hello) result = ui.label("结果会显示在这里") ui.run()

这个代码里最核心的概念是组件创建和事件回调。ui.input 创建输入框,ui.button 创建按钮,on_click 参数绑定点击事件函数。函数体里通过 name_input.value 读取输入框的内容,然后 result.set_text() 更新界面里的文字。整个过程不需要写任何前端路由、API 接口、状态管理,就是一个顺序执行的 Python 脚本,但界面是活的。

如果你要做一个包含循环更新的页面,还可以用 ui.timer 定期刷新,或者用 @ui.refreshable 装饰器把一个组件列表做成可刷新区域。比如说隔一段时间从数据库读一次数据,刷新表格,这个操作在 NiceGUI 里就是几行代码的事。

4.3 部署和性能上的注意事项

NiceGUI 写起来爽,但部署时要清楚它的边界。ui.run() 默认会起一个开发服务器,生产环境你可以通过 host、port、title 参数来控制绑定地址。如果你已经有 FastAPI 应用,也可以把 NiceGUI 挂载到现有应用上,共享同一个端口和进程。

性能上,NiceGUI 的组件是基于 WebSocket 通信的。页面上组件数量不多时很流畅,但如果一个页面塞了上千个组件,或者频繁进行大列表更新,前端会有卡顿感。我建议复杂页面别一把梭,尽量用分页和虚拟滚动,或者把不常变的部分缓存起来。另一个常见坑是回调函数里千万别写耗时的同步操作,比如直接调用一个跑 5 分钟的模型推理,界面会明显卡住。正确做法是用多线程或者 async 异步任务,让界面及时给出“正在处理”的反馈,任务完成后通过 UI 组件更新结果。

5. 第四个库:Textual —— 终端里的“现代化界面”

5.1 TUI 为什么值得重新关注

很多人一听到终端应用,第一反应就是古老的 Curses 或者纯 print。但终端并没有死,运维人员、开发者的日常里,SSH 到服务器上跑命令、看日志、改配置,这些都是高频场景。如果这些操作能用上带布局、带交互、带样式的界面,效率会提升不少。Textual 做的就是这件事,它是 Rich 的作者开发的一个 TUI 框架,让你用类似 CSS 的样式语言和事件机制,在终端里构建应用。

我不是说所有工具都应该 TUI 化,但对特定场景,比如实时日志监控、数据库查询工具、简单的项目管理面板,TUI 比 Web 界面更轻、更快、更符合终端工作流。Textual 还支持鼠标点击和滚动,这在终端里非常罕见。你可以把它理解成“用写 Web 前端的思路去写命令行界面”。

5.2 快速搭建一个带事件的界面

先装库:pip install textual。然后看代码:

from textual.app import App, ComposeResult from textual.containers import Vertical from textual.widgets import Header, Footer, Label, Button class MyApp(App): CSS = """ #content { width: 60; height: 10; border: solid white; align: center middle; } """ def compose(self) -> ComposeResult: yield Header() yield Footer() with Vertical(id="content"): yield Label("你好,Textual") yield Button("退出") def on_button_pressed(self) -> None: self.exit() if __name__ == "__main__": MyApp().run()

compose 负责定义界面结构,CSS 负责布局和样式,on_button_pressed 是事件处理方法。注意事件命名规则:Button 组件会触发 on_button_pressed,Close 按钮触发 on_button_pressed 在不同情况下有不同的参数签名,这一点和 Web 里 addEventListener 的思路不完全相同,但理解之后很有规律。

Textual 还提供了 Table、Tree、Input、DataTable 等丰富组件。我常拿它写一个“服务器日志聚合查看器”,把多个日志文件的最后 N 行拼到一个 DataTable 里,用时间戳排序,支持上下键选择和回车打开详情。这种功能在纯命令行里实现起来很烦,在 Textual 里反而不复杂。

5.3 终端环境带来的坑

第一,旧的 Windows CMD 完全不支持 Textual 的渲染方式,必须在 Windows Terminal 或者 VS Code 终端、iTerm2 这些现代终端里跑。第二,Textual 的 CSS 是简化版,和 Web CSS 不是一回事。它没有 flex 那种完整的弹性布局体系,只能用 width、height、align、grid 等有限属性。想实现复杂自适应布局时要多试几个组合。第三,终端宽度不够时布局会乱。建议在应里对最小宽度做判断,或者调用 self.console.size 提前检测,给用户一个调整窗口的提示。

还有一个体验问题:Textual 应用跑起来后,默认会接管整个终端显示。如果应用中途崩溃,可能会出现终端残留内容的情况。我的习惯是写一个异常捕获,在退出时尽量恢复界面,或者教用户按 Ctrl+C 多次强制退出。

6. 第五个库:FastHTML —— 让 Web 开发回到纯 Python

6.1 FastHTML 的设计逻辑

FastHTML 是 fast.ai 团队今年带来的一个非常“反常规”的 Web 框架。它的核心思路是:不用写 HTML 模板,而是用 Python 函数来直接生成 HTML 结构。这个想法其实和 React 的 JSX 有点像,只不过 JSX 用的是类 XML 语法,FastHTML 完全回到 Python 函数调用。

更关键的是,FastHTML 内建支持 HTMX。HTMX 是一种前端交互方式,它通过在 HTML 属性里声明 hx-get、hx-post、hx-target 这些内容,让页面局部刷新,不再需要手写 JavaScrip。对于一直做后端、写 Python 的开发者来说,这意味着你从简单页面到复杂交互,全程都用 Python 解决,前端门槛基本被抹平了。

这个库适合什么场景?我举三个:快速做 AI 应用 demo,给模型配一个带表单和结果展示的页面;做一个内部管理后台,产品、运营能直接在网页上改配置;或者把原来用 Flask + Jinja2 写的老项目慢慢迁移到更现代、更紧凑的写法。它不是要把 Django 替代掉,而是在“小”和“快”这个维度上,给你多一个选择。

6.2 最小 Web 应用示例

安装 fasthtml 后,可以直接从一个文件开始:

from fasthtml.common import * app, rt = FastHTML() @rt("/") def home(): return Title("FastHTML 示例"), Div( P("欢迎进入 FastHTML 的世界"), Button("点我", hx_get="/hello", hx_target="#content") ) @rt("/hello") def hello(): return P("这是点击后加载的内容") serve()

这里 FastHTML() 帮你搭好了应用和路由,@rt 装饰器定义路由。home 函数返回的内容会被解析成 HTML,Button 上的 hx_get="/hello" 表示点击后向 /hello 发起请求,hx_target="#content" 指把响应内容渲染到 id 为 content 的容器里。整个交互过程中,页面不会整页刷新,感觉像是在用一种轻量级的 React。

模板逻辑也不用担心,Python 函数本身就是天然的模板系统。循环、条件、嵌套,全都用最普通的 Python 语法解决。比如说循环渲染一个列表:

@rt("/list") def list_page(): items = ["a", "b", "c"] return Ul(*[Li(item) for item in items])

Ul 和 Li 都是定义好的构造函数,*列表展开后就是多个 Li 子元素。

6.3 交互和部署注意点

FastHTML 的交互模式高度依赖 HTMX 属性。如果你不熟悉 HTMX,建议先花半天看看官方文档上的“思维模型”部分。它和传统 Web 开发最大的区别是:你不需要在前后端各写一套逻辑,只要在后端返回 HTML 片段,前端自动把这个片段放进去。这会让很多人第一次用时不适应,但一旦掌握,开发效率真的高。

部署方面,FastHTML 底层用的是 ASGI 服务器,所以可以直接用 uvicorn 启动。不过官方提供的 serve() 在开发时有自动重载,生产环境建议你手动用 gunicorn + uvicorn workers 跑,或者设置在非开发模式下。另外,FastHTML 目前版本迭代很快,API 偶尔有变化,我建议查看文档时优先看最新 release 对应的教程而不是网上的旧文章。

有一点要提醒:FastHTML 适合新项目从零开始,但如果你想强行把它塞进一个已经很大的 Flask 项目里,成本可能比收益更高。老的成熟项目还是用原本的模板体系更稳,新项目才适合直接用 FastHTML 这种风格去冲刺速度。

7. 我的选库建议:别追新,要看值不值

7.1 评估新库的五个维度

前面聊了五个具体的库,最后分享一套我能反复使用的评估框架。你不用照抄,但可以参考这五个维度去判断一个库值不值得引入:

维度判断方法我的底线
维护活跃度看 GitHub 最近 release 时间、issue 响应速度三个月内有更新
API 稳定性看 changelog 是否频繁破坏性变更有破坏性变更必须有迁移文档
生态衔接能否和 pandas、FastAPI、Jupyter 互相转换至少能 import 后用一行代码接入
性能收益用你自己的数据跑一个 benchmark,不只看官方宣称至少比旧方案快 2 倍
许可证检查是否宽松开源,适合商用MIT、Apache 2.0 优先

按照这套标准,DuckDB 和 Polars 都已经相当成熟,适合直接进生产环境;NiceGUI 和 Textual 要看团队是否接受新交互模式,本身稳定性足够;FastHTML 更适合新项目或原型阶段,如果要做复杂后台,建议观察几个月再决定是否全量引入。

7.2 两个需要谨慎的场景

第一种是“为了新而新”的迁移。如果现有 pandas 代码稳定运行,数据量也不大,那我不会仅仅因为 Polars 快就把它换掉。迁移本身有成本,测试要重写,团队要重新学,性价比不一定高。第二种是依赖非常深的场景。比如说你有一段代码同时依赖 pandas 的很多奇技淫巧,包括 MultiIndex、resample 的复杂规则、字符串正则的一体化处理,那搬到 Polars 时就要反复验证边界行为。这种情况下我建议先写一个代表性测试集,跑一遍看行为是否一致,再逐步替换。

我自己现在的默认路径是:拿到一批新数据先丢进 DuckDB 跑一遍查询,如果需要复杂特征工程再转 Polars,最后要给人用就把结果接进 NiceGUI。这三个组合应对了我 80% 的日常杂活。从关注新库到使用新库,最关键的一点其实不是它新不新,而是它能不能让你把原本别扭的工作流改得更顺。如果你有同样被某个旧工具卡住的场景,不妨先从这五个里挑一个试起来,成本很低,而且大概率比想象中稳。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进