Rust 让 Python 再次伟大

我先说个昨天晚上的事儿哈。

昨天晚上十一点多,我在公司楼下抽烟,手机那边钉钉就“叮”一下,我们组那个小李问我: “东哥,Python 这服务 CPU 又打满了,要不要干脆用 Rust 重写算了?”

我当时脑子一抽就回他一句:“别瞎重写啊,用 Rust 把 Python 扛不动的那一块儿扛一下,让 Python 再伟大一回就行了。”

你看,这标题不就来了嘛:Rust 让 Python 再次伟大。

先把丑话说前头:Python 到底慢在哪儿

你们是不是也有这种体验:写业务、写脚本、写 Web,Python 那叫一个顺手; 但是一旦遇到那种很吃 CPU 的玩意儿,比如:

  • 一堆循环算加密 / 压缩
  • 批量图片处理
  • 大量 JSON 解析 + 校验
  • 数据清洗,动不动几十万行一刷

top 一看,Python 进程 300% CPU,风扇起飞,对吧。

根子其实就俩词:

  • 解释执行 + GIL(别纠结细节,就当是“写起来爽,算起来累”)
  • 很多库是 Python 写的 for 循环,一层套一层

但你说让大家都去写 C 扩展、写 C++,一堆指针、内存、段错误,谁爱写谁写去。 Rust 就不一样,它刚好踩在一个很尴尬但很舒服的位置上:性能顶、内存安全、工具链顺手。

所以思路其实很简单一句话:Python 负责“说话”,Rust 负责“干活”。

先来个最小可用例子:Python 调一个 Rust 加速函数

那个场景是这样的: 我们有个接口要把一堆数字求和,每次都是几百万个数。 原来小李写的是标准 Python:

# sum_slow.py

def sum_numbers(nums):
    total = 0
    for n in nums:
        total += n
    return total

if __name__ == "__main__":
    data = list(range(10_000_000))
    print(sum_numbers(data))

逻辑一点毛病没有,就是慢。 我跟他说,要不你把这个内层循环丢给 Rust 算,Python 只管把数组丢进去、结果拿出来。

整件事儿的核心其实是一个词:pyo3。 你可以简单理解成“帮你把 Rust 函数变成 Python 模块的那层胶水”。

Rust 这边大概长这样(示意,你不用一行一行背):

// src/lib.rs

use pyo3::prelude::*;

#[pyfunction]
fn sum_numbers_py(nums: Vec<i64>) -> PyResult<i64> {
    let mut total: i64 = 0;
    for n in nums {
        total += n;
    }
    Ok(total)
}

#[pymodule]
fn fast_sum(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_numbers_py, m)?)?;
    Ok(())
}

编译完以后(一般用 maturin 这种工具打个包就行),Python 这边的代码就能摇身一变:

# new_sum.py
from fast_sum import sum_numbers_py

def sum_numbers(nums):
    # 这里直接调用 Rust 实现
    return sum_numbers_py(nums)

if __name__ == "__main__":
    data = list(range(10_000_000))
    print(sum_numbers(data))

对小李来说,他日常写的 Python 代码几乎不用改, 就是 from 的地方换了下,把真正干体力活的那层换成 Rust 写的。

跑出来啥效果呢?具体数字我就不报了,反正肉眼可见的快, 原来接口动不动 2、3 秒,现在几百毫秒搞定。 你说这算不算是 Rust 在背后让 Python“再次伟大”了一把。

为啥不是“全都用 Rust 写算了”

这个问题你们肯定会问,小李也问了。 我当时跟他说,我说你想象一下你们组现在的技术栈:

  • 监控组件、ORM、框架、SDK,全是围着 Python 生态转的
  • 一堆脚本、运维、数据流程也绑在 Python 这套库上
  • 团队里真正在项目里写过 Rust 的,可能就你一个

如果你一拍脑袋说:我们要“全面 Rust 化”,那接下来半年你就不用做业务了,天天在填坑: 部署方式改、监控方案改、日志改、SDK 重写一套,老脚本也得重来。

大部分团队其实只需要这么几件事:

  1. 95% 的业务逻辑依旧 Python,写得开心、上线快
  2. 那 5% 真正的性能瓶颈用 Rust 封一层
  3. 外面暴露出来还是 import 某个 Python 模块,别人感知不到 Rust 的存在

这不就好比啥? 饭店厨房里请了个硬核大厨(Rust),但前厅、收银、菜单(Python)一切照旧, 顾客只会觉得“这菜怎么忽然好吃、上得又快了”,不会关心后厨是谁在炒。

第二种组合拳:Rust 做服务,Python 当“前台”

上面那个是以内嵌扩展的模式(在一个进程里玩)。 有时候你们团队里已经有一帮写 Rust 的同学,他们更习惯搞个独立服务,比如:

  • 用 Rust 写一个超快的校验 / 计算 / 推荐引擎
  • 对外只提供 HTTP / gRPC 接口

那 Python 要做的就更简单了,跟调别的微服务没啥区别。 举个最土的例子,就当 Rust 那头有个 http://rust-core:8080/eval 的接口好了:

import requests

def eval_score(user_id: int, features: dict) -> float:
    payload = {
        "user_id": user_id,
        "features": features,
    }
    resp = requests.post("http://rust-core:8080/eval", json=payload, timeout=0.2)
    resp.raise_for_status()
    data = resp.json()
    return data["score"]

if __name__ == "__main__":
    score = eval_score(123, {"age": 29, "vip": True})
    print("score =", score)

这就完全当普通 HTTP 服务在用了。 差别只是: 平时你调用的是 Python 写的微服务,这次换成 Rust 写的而已。

优点是啥?

  • 语言隔离,崩了也不会直接带走你的 Python Web 进程
  • Rust 那边可以随便用多线程、多核,完全躲开 GIL 的限制
  • 部署上直接当一个新的服务,滚动发布也简单

缺点也有:网络开销、序列化开销,但很多重逻辑场景这点成本是可以接受的。

还有一种更“懒”的玩法:直接薅生态的羊毛

这个其实你们已经天天在干,只是没注意它是 Rust 写的。 现在好多 Python 圈的“快库”,其实背后都是 Rust:

  • polars:一个特别快的数据分析库,很多人拿它当 pandas 替代
  • 一堆 JSON / 校验 / 解析相关的库,底层也在慢慢往 Rust 挪
  • 静态检查、打包、依赖解析那一票工具里,Rust 也越混越熟

比如你要做个很简单的分析,小李原来写 pandas 是这样:

import pandas as pd

df = pd.read_csv("users.csv")
active_df = df[df["active"] == 1]
print(active_df.groupby("city")["id"].count())

后来他尝试了一下 polars,写法有点不一样,但思路挺像:

import polars as pl

df = pl.read_csv("users.csv")
active_df = df.filter(pl.col("active") == 1)
result = active_df.groupby("city").agg(pl.count())
print(result)

在数据量比较大的时候,你会发现 polars 就明显快很多, 但是对 Python 选手来说,学习成本并不离谱, 你要记住的事其实只有一句:“这是一个 Rust 写的高性能库,我拿 Python 来调它。”

你看,同样是利用 Rust 的性能,一种是你自己写扩展; 另一种是直接用别人已经帮你封装好的轮子。

来个稍微像样点的小实战:用 Rust 加速 Python 的简单打分逻辑

说个我们线上真干过的活,只不过我给你简化了一下。

背景: 有个实时接口,需要根据用户的一长串行为记录算一个“活跃度分数”, 大概类似:

def calc_score(events):
    score = 0.0
    for e in events:
        if e["type"] == "login":
            score += 1.0
        elif e["type"] == "pay":
            score += 5.0 * e.get("amount", 0)
        elif e["type"] == "comment":
            score += 0.5
        # ... 乱七八糟很多规则
    return score

问题不在逻辑,而在量:一个请求里 events 可能就上千条, 每秒几百个请求打进来,这点小循环撑不住。

思路就是把这段循环搬到 Rust,Python 负责准备好“干净”的结构。 比如 Python 这边改成这样(假设 Rust 那头导出了一个 calc_score_batch):

from fast_score import calc_score_batch  # Rust 导出的函数

def calc_score(events):
    # 先把复杂的 dict 结构,转换成比较规整的列表
    types = []
    amounts = []
    for e in events:
        t = e.get("type")
        a = float(e.get("amount", 0) or 0)
        types.append(t)
        amounts.append(a)

    # 交给 Rust 批量算
    return calc_score_batch(types, amounts)

Rust 那头大概就是绕着两个 Vec 做循环, 这里我就不写完整了,反正结构跟前面 sum 那个类似, 只是多了一点枚举匹配的逻辑而已。

这事儿做完以后,最大的变化其实不是“快了多少倍”, 而是整个 Python 项目再也没有人为那个算分函数“微优化”了。 你知道那种感觉吧:以前大家会各种:

  • 换成 list comprehension
  • 想办法把 if-else 拆成字典查表
  • 加缓存、加短路、改数据结构

做完 Rust 扩展之后,就没人想这些歪门邪道了, 接口慢了?优先考虑是不是别的地方出了问题, 这个模块就“稳定快速”,不用动它。

说点落地的:真要上手,大概要搞些什么

这块我简单念一嘴,你回头真要干,可以按这个方向查文档:

  1. 想在一个进程里玩(Python import Rust 模块那种)
  • 查 pyo3 + maturin
  • Rust 这边写 #[pyfunction]、#[pymodule] 那套
  • Python 这边就当普通模块用
  • 想走服务化
    • Rust 那边随便选个 Web 框架(actix / axum 之类)
    • Python 用 requests / httpx 去调
    • 配合现有微服务体系就行
  • 想偷懒
    • 直接搜“xxx rust python binding”
    • 看有没有社区已经写好的库,比如 polars 这种

    重点是别一上来就搞得太大,挑一个最痛的热点先动一刀, 尝到甜头之后,再慢慢把其他瓶颈挪过去。

    顺嘴说一句坑

    Rust 虽然好,但也不是“银弹”,有几个坑你提早心里有数:

    • 调试难度比纯 Python 大一点,尤其是跨语言调用时
    • 编译时间会比你 pip 装个纯 Python 包要长
    • 团队里总得有一两个真写得动 Rust 的,不然出了 bug 会很尴尬

    所以我一般跟同事说: “别把 Rust 当目标,把性能当目标。Rust 只是当前看起来比较顺眼的那个方案。”

    行了我不唠了,再唠下去该饿了,我还得去热个夜宵。 你要是真打算搞一把 Rust + Python 的组合,回头可以把你们的业务场景丢给我,我帮你一起想想先砍哪一刀比较划算。

    原文链接:https://mp.weixin.qq.com/s/iMrWdwuuhHNX8Jx4Fp6kIw

    Zblog
    YourCompany, Mitchell Admin 2026年1月5日
    分析这篇文章
    标签
    我们的博客
    存档
    Python 3.14 自由线程实战