MoreRSS

site iconPython猫修改

运营「Python 潮流周刊」。Python编程、生活随笔、个人作品。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Python猫的 RSS 预览

Python 潮流周刊#113:用虚拟线程取代 async/await

2025-08-02 08:00:00

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。

温馨提示: 在微信关注 Python猫,发送数字“9”,即可领取 9 折优惠码,订阅专栏可享 15 元优惠。

去专栏阅读全文:全文链接

分享了 12 篇文章,12 个开源项目,1 则音视频

以下是本期标题摘要:

🦄文章&教程

① 探秘 CPython 3.14 远程调试

② 深入解释器:Python 的攻击性应用

③ 从 Async/Await 到虚拟线程

④ Python JIT 编译器的后续进展

⑤ asyncio:一个充满痛点的库

⑥ 开发自主性系统:AI 智能体工作流指南

⑦ 大模型架构深度对比:从 DeepSeek-V3 到 Kimi K2

⑧ 用 Python + uv + pytest 打造自动化方案

⑨ PyPI 钓鱼攻击事件报告

⑩ 一行注释也能执行 Python 代码?

⑪ 深入理解 Flask 应用与请求上下文机制

⑫ AI 是否正在抛弃 Python 社区?

🐿️项目&资源

① mediacms:视频和媒体管理系统

② darkdump:深度网络抓取的情报分析工具

③ ii-agent:开发和部署 AI 智能体的框架

④ copyparty:便携式文件服务器

⑤ ai-cookbook:AI 系统开发示例和教程

⑥ agno:多智能体系统全栈框架

⑦ abogen:文本转有声书生成器

⑧ CCPlugins:Claude Code 生产力插件

⑨ LangExtract:结构化信息提取库

⑩ PicTex:创建精美文本图像的 Python 库

⑪ html-to-markdown:现代化的 HTML 转 Markdown 转换器

⑫ erys: Jupyter Notebook 的终端交互界面

🐢播客&视频

① Talk Python To Me 第513期:Python 历史故事

周刊实行付费订阅制,年费 148 元,平均每天 4 毛钱,为你精准筛选高质量技术内容。在信息洪流中为你淘金,助力技术视野拓展和职业发展,欢迎订阅:https://xiaobot.net/p/python_weekly

订阅后,可免费查看 第 113 期周刊的全文:https://www.xiaobot.net/post/9b21ec46-0786-4d6a-b95d-e997f9f0ebbc

Python 潮流周刊第3季总结,附电子书下载

Python 潮流周刊第二季完结(31~60)

Python 潮流周刊第一季精华合集(1~30)

微信关注 Python猫https://img.pythoncat.top/python_cat.jpg

🎯关联阅读

上一期:Python 潮流周刊#112:欢迎 AI 时代的编程新人

Python 潮流周刊#112:欢迎 AI 时代的编程新人

2025-07-26 08:00:00

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。

温馨提示: 在微信关注 Python猫,发送数字“9”,即可领取 9 折优惠码,订阅专栏可享 15 元优惠。

去专栏阅读全文:全文链接

分享了 13 篇文章,12 个开源项目

以下是本期标题摘要:

🦄文章&教程

① Python 列表推导式的解包操作

② 使用 AtomicDict 进行多线程聚合

③ PEP 799:专用的 profilers 包来整合 Python 性能分析工具

④ Python 元编程层

⑤ 探索 Python 3.14 的远程调试协议

⑥ 如何避免 Django 中的 N+1 查询问题

⑦ Toad:终端中 AI 智能体编程的通用 UI

⑧ Python 打包工具完整指南 2025

⑨ Python 社区拥抱 AI 时代的编程新人

⑩ Python 中"Parse, don't validate"的含义

⑪ 修复 Python 管道输出中的 BrokenPipeError

⑫ 我最喜欢的 AI 应用场景是写日志

⑬ 如何理解编程中的时间?

🐿️项目&资源

① Qwen3-Coder:阿里最新开源编程大模型

② awesome-claude-code:Claude Code 精选资源集合

③ open_deep_research:开源深度研究 AI 智能体

④ PhotoshopAPI:高性能的 Photoshop 文件解析器

⑤ LanceDB:多模态 AI 数据湖仓

⑥ MediaManager:自托管的媒体管理系统

⑦ fstrings.wtf:Python F-String 知识测验

⑧ complexipy:Python 代码复杂度分析工具

⑨ ART:AI 智能体强化学习训练器

⑩ NeuralAgent:桌面 AI 智能体助手

⑪ SkyReels-V2:无限长度视频生成模型

⑫ gitingest:将 Git 仓转换为适合 LLM 的文本摘要

周刊实行付费订阅制,年费 148 元,平均每天 4 毛钱,为你精准筛选高质量技术内容。在信息洪流中为你淘金,助力技术视野拓展和职业发展,欢迎订阅:https://xiaobot.net/p/python_weekly

订阅后,可免费查看 第 112 期周刊的全文:https://www.xiaobot.net/post/8160f6ba-ff62-4cdd-8023-9a191cbc47ed

Python 潮流周刊第3季总结,附电子书下载

Python 潮流周刊第二季完结(31~60)

Python 潮流周刊第一季精华合集(1~30)

微信关注 Python猫https://img.pythoncat.top/python_cat.jpg

🐠关联阅读

上一期:Python 潮流周刊#111:Django迎来 20 周年、OpenAI 前员工分享工作体验

下一期:Python 潮流周刊#113:113:用虚拟线程取代 async/await

Python 潮流周刊#111:Django迎来 20 周年、OpenAI 前员工分享工作体验

2025-07-19 08:00:00

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。

温馨提示: 在微信关注 Python猫,发送数字“9”,即可领取 9 折优惠码,订阅专栏可享 15 元优惠。

去专栏阅读全文:全文链接

分享了 13 篇文章,13 个开源项目,1 则音视频

以下是本期标题摘要:

🦄文章&教程

① Django 20 周年快乐!

② 用 Python 探索 Llama Stack:工具调用和智能体

③ 从 SQL 到 SQLModel:Python 操作数据库的更简洁方式

④ 有时候,继承胜过组合

⑤ 2025 年的 Python 和 Make

⑥ 用 Asyncio Protocols 开发简单 HTTP 服务器

⑦ 面向数据科学家的 DuckDB 深度解析

⑧ 我正在转向 Python 并且真的喜欢它

⑨ 2048:迭代器和可迭代对象

⑩ 如何严重搞错 Django 外键?

⑪ Python 日志记录:可观测性实用指南

⑫ 前员工分享 OpenAI 工作体验

⑬ 关于在开源软件中使用大模型的思考

🐿️项目&资源

① Kimi-K2:先进的智能代理大模型

② BrowserOS:开源智能代理浏览器

③ macOS-use:让 AI 代理控制 Mac 应用

④ SuperClaude:Claude Code 增强框架

⑤ pymanager:Python 安装管理器

⑥ mcp-for-beginners:微软推出的 MCP 入门教程

⑦ acp:新的 AI 智能体通信协议

⑧ RapidOCR:多平台多语言 OCR 工具包

⑨ Voxtral:开源语音理解模型

⑩ html-to-markdown:现代化 HTML 转 Markdown 工具

⑪ aiosqlitepool:高性能异步 SQLite 连接池

⑫ epub-translator:AI 驱动的 EPUB 电子书翻译工具

⑬ ai-engineering-hub:LLM 和 AI 智能体深度教程

🐢播客&视频

① PyData London 2025 视频合集(50个)

周刊实行付费订阅制,年费 148 元,平均每天 4 毛钱,为你精准筛选高质量技术内容。在信息洪流中为你淘金,助力技术视野拓展和职业发展,欢迎订阅:https://xiaobot.net/p/python_weekly

订阅后,可免费查看 第 111 期周刊的全文:https://www.xiaobot.net/post/28dfc557-09c9-4ca2-8f49-4e9231415908

Python 潮流周刊第3季总结,附电子书下载

Python 潮流周刊第二季完结(31~60)

Python 潮流周刊第一季精华合集(1~30)

微信关注 Python猫https://img.pythoncat.top/python_cat.jpg

🌻关联阅读

上一期:Python 潮流周刊#110:JIT 编译器两年回顾,AI 智能体工具大爆发

下一期:Python 潮流周刊#112:欢迎 AI 时代的编程新人

Python 潮流周刊#110:JIT 编译器两年回顾,AI 智能体工具大爆发

2025-07-12 08:00:00

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。

温馨提示: 在微信关注 Python猫,发送数字“9”,即可领取 9 折优惠码,订阅专栏可享 15 元优惠。

分享了 12 篇文章,12 个开源项目。去专栏阅读全文:全文链接

以下是本期标题摘要:

🦄文章&教程

① CPython JIT 编译器两年回顾

② 类型系统正在改变 Python

③ 用 uv 依赖解析器解决 Wordle

④ 使用 FastAPI、MongoDB 和 WebSockets 开发实时仪表板

⑤ Python 类型检查中的逆变问题解决方案

⑥ LangFlow 教程:用可视化工作流构建生产级 AI 应用

⑦ FastAPI 推荐服务的扩展实战

⑧ 让扩展适配自由线程 Python

⑨ Augmented Coding:超越感觉

⑩ 智能体的上下文工程

⑪ PyTorch 已死,JAX 万岁

⑫ 操作者,而非用户和程序员

🐿️项目&资源

① trae-agent:基于 LLM 的通用软件工程智能体

② xai-sdk-python:xAI 官方 Python SDK

③ desto:tmux 会话中的脚本管理工具

④ devin.cursorrules:将 Cursor 转换为 Devin 级 AI 助手

⑤ LingChat:灵动的 AI 聊天陪伴助手

⑥ rowboat:AI 驱动的多智能体构建器

⑦ agent-factory:智能体工作流代码生成工具

⑧ throttled-py:高性能 Python 限流库

⑨ tubearchivist:自托管 YouTube 媒体服务

⑩ mlx-audio:基于 Apple MLX 框架的语音处理库

⑪ sloppy-xml-py: 容错性 XML 解析库

⑫ Python-Scripts: 自动化任务 Python 脚本集合

周刊实行付费订阅制,年费 148 元,平均每天 4 毛钱,为你精准筛选高质量技术内容。在信息洪流中为你淘金,助力技术视野拓展和职业发展,欢迎订阅:https://xiaobot.net/p/python_weekly

订阅后,可免费查看 第 110 期周刊的全文:https://www.xiaobot.net/post/a3c4b6b4-b60b-4d43-86b1-c179bd361cc7

Python 潮流周刊第3季总结,附电子书下载

Python 潮流周刊第二季完结(31~60)

Python 潮流周刊第一季精华合集(1~30)

微信关注 Python猫https://img.pythoncat.top/python_cat.jpg

⭐关联阅读

上一期:Python 潮流周刊#109:Python 性能优化技巧

下一期:Python 潮流周刊#111:Django迎来 20 周年、OpenAI 前员工分享工作体验

让代码飙升330倍:从入门到精通的四种性能优化实践

2025-07-09 08:00:00

花下猫语:性能优化是每个程序员的必修课,但你是否想过,除了更换算法,还有哪些“大招”?这篇文章堪称典范,它将一个普通的函数,通过四套组合拳,硬生生把性能提升了 330 倍!作者不仅展示了“术”,更传授了“道”。让我们一起跟随作者的思路,体验一次酣畅淋漓的优化之旅。

PS.本文选自最新一期Python 潮流周刊,如果你对优质文章感兴趣,诚心推荐你订阅我们的专栏。


作者:Itamar Turner-Trauring

译者:豌豆花下猫@Python猫

英文:330× faster: Four different ways to speed up your code

声明:本翻译是出于交流学习的目的,为便于阅读,部分内容略有改动。转载请保留作者信息。

温馨提示: 本文原始版本与当前略有不同,比如曾经提到过500倍加速;本文已根据实际情况重新梳理,使论证更清晰。

当你的 Python 代码慢如蜗牛,而你渴望它快如闪电时,其实有很多种提速方式,从并行化到编译扩展应有尽有。如果只盯着一种方法,往往会错失良机,最终的代码也难以达到极致性能。

为了不错过任何潜在的提速机会,我们可以从“实践”的角度来思考。每种实践:

  • 以独特方式加速你的代码
  • 涉及不同的技能和知识
  • 可以单独应用
  • 也可以组合应用,获得更大提升

为了让这一点更具体,本文将通过一个案例演示多种实践的应用,具体包括:

  1. 效率(Efficiency): 消除浪费或重复的计算。
  2. 编译(Compilation): 利用编译型语言,并巧妙绕开编译器限制。
  3. 并行化(Parallelism): 充分发挥多核CPU的威力。
  4. 流程(Process): 采用能产出更快代码的开发流程。

我们将看到:

  • 仅用效率实践,就能带来近 2倍 提速。
  • 仅用编译实践,可实现 10倍 提速。
  • 两者结合,速度更上一层楼。
  • 最后加上并行化实践,最终实现 330倍 惊人加速。

我们的例子:统计字母频率

我们有一本英文书,简·奥斯汀的《诺桑觉寺》:

with open("northanger_abbey.txt") as f:
    TEXT = f.read()

我们的目标是分析书中字母的相对频率。元音比辅音更常见吗?哪个元音最常见?

下面是最初的实现:

from collections import defaultdict

def frequency_1(text):
    # 一个当键不存在时默认值为0的字典
    counts = defaultdict(lambda: 0)
    for character in text:
        if character.isalpha():
            counts[character.lower()] += 1
    return counts

运行结果如下:

sorted(
    (count, letter) for (letter, count)
    in frequency_1(TEXT).items()
)
[(1, 'à'),
 (2, 'é'),
 (3, 'ê'),
 (111, 'z'),
 (419, 'q'),
 (471, 'j'),
 (561, 'x'),
 (2016, 'k'),
 (3530, 'v'),
 (5297, 'b'),
 (5404, 'p'),
 (6606, 'g'),
 (7639, 'w'),
 (7746, 'f'),
 (7806, 'y'),
 (8106, 'c'),
 (8628, 'm'),
 (9690, 'u'),
 (13431, 'l'),
 (14164, 'd'),
 (20675, 's'),
 (21107, 'r'),
 (21474, 'h'),
 (22862, 'i'),
 (24670, 'n'),
 (26385, 'a'),
 (26412, 'o'),
 (30003, 't'),
 (44251, 'e')]

毫无意外,出现频率最高的字母是 "e"。

那我们如何让这个函数更快?

流程实践:测量与测试

软件开发不仅依赖于源代码、库、解释器、编译器这些“产物”,更离不开你的工作“流程”——也就是你做事的方法。性能优化同样如此。本文将介绍两种在优化过程中必不可少的流程实践:

  1. 通过基准测试和性能分析来测量代码速度。
  2. 测试优化后的代码,确保其行为与原始版本一致。

我们可以先用 line_profiler 工具分析函数,找出最耗时的代码行:

Line #      Hits   % Time  Line Contents
========================================
     3                     def frequency_1(text):
     4                         # 一个当键不存在时默认值为0的字典
     5                         # available:
     6         1      0.0      counts = defaultdict(lambda: 0)
     7    433070     30.4      for character in text:
     8    433069     27.3          if character.isalpha():
     9    339470     42.2              counts[character.lower()] += 1
    10         1      0.0      return counts

效率实践:减少无用功

效率实践的核心,是用更少的工作量获得同样的结果。这类优化通常在较高的抽象层面进行,无需关心底层CPU细节,因此适用于大多数编程语言。其本质是通过改变计算逻辑来减少浪费。

减少内循环的工作量

从上面的性能分析可以看出,函数大部分时间都花在 counts[character.lower()] += 1 这行。显然,对每个字母都调用 character.lower() 是种浪费。我们一遍遍地把 "I" 转成 "i",甚至还把 "i" 转成 "i"。

优化思路:我们可以先分别统计大写和小写字母的数量,最后再合并,而不是每次都做小写转换。

def frequency_2(text):
    split_counts = defaultdict(lambda: 0)
    for character in text:
        if character.isalpha():
            split_counts[character] += 1

    counts = defaultdict(lambda: 0)
    for character, num in split_counts.items():
        counts[character.lower()] += num
    return counts

# 确保新函数结果与旧函数完全一致
assert frequency_1(TEXT) == frequency_2(TEXT)

说明:这里的 assert 就是流程实践的一部分。一个更快但结果错误的函数毫无意义。虽然你在最终文章里看不到这些断言,但它们在开发时帮我抓出了不少bug。

基准测试(也是流程实践的一环)显示,这个优化确实让代码更快了:

| frequency_1(TEXT) | 34,592.5 µs | | frequency_2(TEXT) | 25,798.6 µs |

针对特定数据和目标进行优化

我们继续用效率实践,这次针对具体目标和数据进一步优化。来看下最新代码的性能分析:

Line #      Hits   % Time  Line Contents
========================================
     3                     def frequency_2(text):
     4         1      0.0      split_counts = defaultdict(lambda: 0)
     5    433070     33.6      for character in text:
     6    433069     32.7          if character.isalpha():
     7    339470     33.7              split_counts[character] += 1
     8
     9         1      0.0      counts = defaultdict(lambda: 0)
    10        53      0.0      for character, num in split_counts.items():
    11        52      0.0          counts[character.lower()] += num
    12         1      0.0      return counts

可以看到,split_counts[character] += 1 依然是耗时大户。怎么加速?答案是用 list 替换 defaultdict(本质上是 dict)。list 的索引速度远快于 dict

  • list 存储条目只需一次数组索引
  • dict 需要计算哈希、可能多次比较,还要内部数组索引

list 的索引必须是整数,不能像 dict 那样用字符串,所以我们要把字符转成数字。幸运的是,每个字符都能用 ord() 查到数值:

ord('a'), ord('z'), ord('A'), ord('Z')
# (97, 122, 65, 90)

chr() 还能把数值转回字符:

chr(97), chr(122)
# ('a', 'z')

所以可以用 my_list[ord(character)] += 1 计数。但前提是我们得提前知道 list 的大小。如果处理任意字母字符,list 可能会很大:

ideograph = '𫞉'
ord(ideograph), ideograph.isalpha()
# (178057, True)

再回顾下我们的目标:

  1. 处理对象是英文文本,这是题目要求。
  2. 输出结果里确实有少量非标准英文字母(如 'à'),但极其罕见。(严格说 'à' 应该归为 'a',但这里偷懒没做……)
  3. 我们只关心相对频率,不是绝对精确计数。

基于这些,我决定简化问题:只统计 'A' 到 'Z',其他字符都忽略,包括带重音的。对英文文本来说,这几乎不影响字母相对频率。

这样问题就简单了:字符集有限且已知,可以放心用 list 替代 dict

优化后实现如下:

def frequency_3(text):
    # 创建长度为128的零列表;ord('z')是122,128足够了
    split_counts = [0] * 128
    for character in text:
        index = ord(character)
        if index < 128:
            split_counts[index] += 1

    counts = {}
    for letter in 'abcdefghijklmnopqrstuvwxyz':
        counts[letter] = (
            split_counts[ord(letter)] +
            split_counts[ord(letter.upper())]
        )
    return counts

由于输出只包含A到Z,正确性检查也要稍作调整:

def assert_matches(counts1, counts2):
    """确保A到Z的计数匹配"""
    for character in 'abcdefghijklmnopqrstuvwxyz':
        assert counts1[character] == counts2[character]

assert_matches(
    frequency_1(TEXT),
    frequency_3(TEXT)
)

新实现更快了:

| frequency_2(TEXT) | 25,965.5 µs | | frequency_3(TEXT) | 19,443.5 µs |

编译实践:切换到更快的语言

接下来我们切换到编译型语言——Rust。

其实可以直接把 frequency_1() 移植到 Rust,编译器会自动做一些在 Python 里需要手动优化的事。

但大多数时候,无论用什么语言,效率实践都得靠你自己。这也是为什么“效率”和“编译”是两种不同的实践:它们带来的性能提升来源不同。我们在 frequency_2()frequency_3() 里做的优化,同样能让 Rust 代码更快。

为证明这一点,我把上面三个 Python 函数都移植到了 Rust(前两个源码可点击展开查看):🦄

前两个版本在 Rust 中的实现
#[pyfunction]
fn frequency_1_rust(
    text: &str,
) -> PyResult<HashMap<char, u32>> {
    let mut counts = HashMap::new();
    for character in text.chars() {
        if character.is_alphabetic() {
            *counts
                .entry(
                    character
                        .to_lowercase()
                        .next()
                        .unwrap_or(character),
                )
                .or_default() += 1;
        }
    }
    Ok(counts)
}

#[pyfunction]
fn frequency_2_rust(
    text: &str,
) -> PyResult<HashMap<char, u32>> {
    let mut split_counts: HashMap<char, u32> =
        HashMap::new();
    for character in text.chars() {
        if character.is_alphabetic() {
            *split_counts.entry(character).or_default() +=
                1;
        }
    }

    let mut counts = HashMap::new();
    for (character, num) in split_counts.drain() {
        *counts
            .entry(
                character
                    .to_lowercase()
                    .next()
                    .unwrap_or(character),
            )
            .or_default() += num;
    }
    Ok(counts)
}

第三个版本在 Rust 里的样子:

fn ascii_arr_to_letter_map(
    split_counts: [u32; 128],
) -> HashMap<char, u32> {
    let mut counts: HashMap<char, u32> = HashMap::new();
    for index in ('a' as usize)..=('z' as usize) {
        let character =
            char::from_u32(index as u32).unwrap();
        let upper_index =
            character.to_ascii_uppercase() as usize;
        counts.insert(
            character,
            split_counts[index] + split_counts[upper_index],
        );
    }
    counts
}

#[pyfunction]
fn frequency_3_rust(text: &str) -> HashMap<char, u32> {
    let mut split_counts = [0u32; 128];
    for character in text.chars() {
        let character = character as usize;
        if character < 128 {
            split_counts[character] += 1;
        }
    }

    ascii_arr_to_letter_map(split_counts)
}

所有三个 Rust 版本的结果都和 Python 版本一致:

assert_matches(frequency_1(TEXT), frequency_1_rust(TEXT))
assert_matches(frequency_1(TEXT), frequency_2_rust(TEXT))
assert_matches(frequency_1(TEXT), frequency_3_rust(TEXT))

对所有6个版本做基准测试,清楚地说明了效率实践编译实践的性能优势是不同且互补的。能加速 Python 代码的效率优化,同样也能加速 Rust 代码。

函数 运行时间 (µs)
frequency_1(TEXT) 33,741.5
frequency_2(TEXT) 25,797.4
frequency_3(TEXT) 19,432.0
frequency_1_rust(TEXT) 3,704.3
frequency_2_rust(TEXT) 3,504.8
frequency_3_rust(TEXT) 204.9

一句话:效率和编译是两种不同的速度来源。

并行化实践:榨干多核CPU

到目前为止,代码都只跑在单核CPU上。但现在的电脑大多有多核,利用并行计算又是另一种速度来源,所以它也是独立的实践。

下面是用 Rayon 库 实现的 Rust 并行版本:

fn sum(mut a: [u32; 128], b: [u32; 128]) -> [u32; 128] {
    for i in 0..128 {
        a[i] += b[i];
    }
    a
}

#[pyfunction]
fn frequency_parallel_rust(
    py: Python<'_>,
    text: &str,
) -> HashMap<char, u32> {
    use rayon::prelude::*;

    // 确保释放全局解释器锁(GIL)
    let split_counts = py.allow_threads(|| {
        // 一个榨取 Rayon 更多性能的技巧:
        // 我们关心的 ASCII 字符总是由单个字节明确表示。
        // 所以直接处理字节是安全的,这能让我们强制 Rayon 使用数据块。
        text.as_bytes()
            // 并行迭代数据块
            .par_chunks(8192)
            .fold_with(
                [0u32; 128],
                |mut split_counts, characters| {
                    for character in characters {
                        if *character < 128 {
                            split_counts
                                [*character as usize] += 1;
                        };
                    }
                    split_counts
                },
            )
            // 合并所有数据块的结果
            .reduce(|| [0u32; 128], sum)
    });
    ascii_arr_to_letter_map(split_counts)
}

结果依然正确:

assert_matches(frequency_1(TEXT), frequency_parallel_rust(TEXT))

加速效果如下:

| frequency_3_rust(TEXT) | 234.5 µs | | frequency_parallel_rust(TEXT) | 105.3 µs |

流程重访:我们测对了吗?

最终函数快了330倍……真的吗?

我们是通过多次调用函数取平均运行时间来测量性能的。但我恰好知道一些背景知识:

  • Rust 字符串是 UTF-8,Python 用的是自己的内部格式,不是 UTF-8。
  • 所以调用 Rust 函数时,Python 需要把字符串转成 UTF-8。
  • Python 用特定 API 转 UTF-8 时会缓存转换结果

这意味着,我们很可能没测到 UTF-8 转换的成本,因为反复对同一个 TEXT 字符串基准测试,第一次后 UTF-8 版本就被缓存了。真实场景下,未必总有缓存。

我们可以测下单次调用新字符串的耗时。我用非并行版本,因为它速度更稳定:

from time import time

def timeit(f, *args):
    start = time()
    f(*args)
    print("Elapsed:", int((time() - start) * 1_000_000), "µs")

print("Original text")
timeit(frequency_3_rust, TEXT)
timeit(frequency_3_rust, TEXT)
print()

for i in range(3):
    # 新字符串
    s = TEXT + str(i)
    print("New text", i + 1)
    timeit(frequency_3_rust, s)
    timeit(frequency_3_rust, s)
    print()
Original text
Elapsed: 212 µs
Elapsed: 206 µs

New text 1
Elapsed: 769 µs
Elapsed: 207 µs

New text 2
Elapsed: 599 µs
Elapsed: 202 µs

New text 3
Elapsed: 625 µs
Elapsed: 200 µs

对于新字符串,第一次运行比第二次慢了大约 400µs,这很可能就是转换为 UTF-8 的成本。🦄

当然,我们加载的书本身就是 UTF-8 格式。所以,我们可以改变 API,直接将 UTF-8 编码的 bytes 传递给 Rust 代码,而不是先加载到 Python(转换为 Python 字符串),再传递给 Rust(转换回 UTF-8),这样就能避免转换开销。

我实现了一个新函数 frequency_3_rust_bytes(),它接受 UTF-8 编码的字节(源码略,与 frequency_3_rust() 基本一样)。然后测了下单个字节串第一次和第二次的时间:

with open("northanger_abbey.txt", "rb") as f:
    TEXT_BYTES = f.read()

assert_matches(
    frequency_1(TEXT),
    frequency_3_rust_bytes(TEXT_BYTES)
)

print("新文本不再有~400µs的转换开销:")
new_text = TEXT_BYTES + b"!"
timeit(frequency_3_rust_bytes, new_text)
timeit(frequency_3_rust_bytes, new_text)
新文本不再有~400µs的转换开销:
Elapsed: 186 µs
Elapsed: 182 µs

如果我们测量持续的平均时间,可以看到它与之前的版本大致相当:

| frequency_3_rust(TEXT) | 227.2 µs | | frequency_3_rust_bytes(TEXT_BYTES) | 183.8 µs |

可见传入 bytes 确实能绕过 UTF-8 转换成本。你可能还想实现 frequency_parallel_rust_bytes(),这样并行也能无转换开销。

补充:那么 collections.Counter 呢?

你可能会问,Python 标准库里不是有现成的 collections.Counter 吗?它是专门计数的 dict 子类。

# 来自 Python 3.13 的 collections/__init__.py
def _count_elements(mapping, iterable):
    'Tally elements from the iterable.'
    mapping_get = mapping.get
    for elem in iterable:
        mapping[elem] = mapping_get(elem, 0) + 1

try:
    # 如果可用,加载 C 语言实现的辅助函数
    from _collections import _count_elements
except ImportError:
    pass

class Counter(dict):
    # ...

我们可以这样使用它:

from collections import Counter

def frequency_counter(text):
    return Counter(c.lower() for c in text if c.isalpha())

# 注意:这里的实现与原文略有不同,是为了与 frequency_1 保持完全一致的行为
# 原文的 Counter(text.lower()) 会统计非字母字符,导致结果不一致
assert_matches(frequency_1(TEXT), frequency_counter(TEXT))

这个实现比我们的第一个版本更简洁,但性能如何?

| frequency_1(TEXT) | 34,592.5 µs | | frequency_counter(TEXT) | 约 30,000 µs |

Counter 确实比我们的初始实现快点,但远不如最终优化版。这说明:即使标准库的优化实现,也可能比不上针对场景深度优化的代码。

当然,Counter 胜在简洁和可读性。很多对性能没极致要求的场景,这种权衡完全值得。

性能实践:相辅相成

全文其实一直在用“流程”实践:测试新版本正确性、做性能分析和测量。基准测试还帮我排除了不少无效优化,这里就不赘述了。

“效率”实践帮我们消除无用功,“编译”让代码更快,“并行化”则让多核CPU火力全开。每种实践都是独特的、能带来乘数效应的速度来源。

一句话:如果你想让代码更快,别只盯着一种实践,多管齐下,速度才会飞起来!


Python猫注:如果你喜欢这篇文章,那我要向你推荐一下 Python 潮流周刊!创刊仅两年,我们已坚持分享了超过 1300+ 篇优质文章,以及 1200+ 个开源项目或工具资源,每周精选,助力你打破信息差,告别信息过载,成为更优秀的人!

Python 潮流周刊#109:Python 性能优化技巧

2025-07-05 08:00:00

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。

温馨提示: 在微信关注 Python猫,发送数字“9”,即可领取 9 折优惠码,订阅专栏可享 15 元优惠。

分享了 13 篇文章,13 个开源项目

以下是本期标题摘要:

🦄文章&教程

① 330 倍提升:四种性能优化方法

② 为什么这个 Python 性能技巧不再重要

③ Python 3.14 的进一步性能进化:Tail Call Interpreter

④ 常用库中不常见的 Python 用法

⑤ 代码阅读:Python 标准库模块 shelve.py

⑥ Anthropic 是如何开发多智能体研究系统?

⑦ LLM 时代的编程语言设计:回归平庸?

⑧ Python 全局变量在字节码中的工作原理

⑨ 万字长文彻底剖析 Python 正则表达式

⑩ Pyodide 中的 JavaScript Promise 集成

⑪ 编写玩具软件是一种乐趣

⑫ 我对各种 AI Coding Agent 工具的看法

⑬ Vibe Coding 一段时间后的感受

🐿️项目&资源

① PythonLink:Python 精选优质资源导航站

② spegel:AI 驱动的终端网页浏览器

③ autoMate:类 Manus 的 AI 本地自动化助手

④ claude-code:终端中的智能编程助手

⑤ ebook2audiobook:电子书转有声书工具

⑥ mcp:AWS MCP 服务器套件

⑦ happy-llm:大语言模型原理与实践教程

⑧ Office-PowerPoint-MCP-Server:PowerPoint MCP 服务器

⑨ octopusdash:动态 Django 管理面板

⑩ cognee:5 行代码实现 AI 智能体记忆

⑪ FastAPI-boilerplate:FastAPI 异步 API 模板

⑫ spy-search:比 Perplexity 更快的 LLM 搜索引擎

⑬ premier:灵活轻量的 API 网关

周刊实行付费订阅制,年费 148 元,平均每天 4 毛钱,为你精准筛选高质量技术内容。在信息洪流中为你淘金,助力技术视野拓展和职业发展,欢迎订阅:https://xiaobot.net/p/python_weekly

订阅后,可免费查看 第 109 期周刊的全文:https://www.xiaobot.net/post/dd1f1826-bfd8-4251-b539-e9c57f6f0795

Python 潮流周刊第3季总结,附电子书下载

Python 潮流周刊第二季完结(31~60)

Python 潮流周刊第一季精华合集(1~30)

微信关注 Python猫https://img.pythoncat.top/python_cat.jpg

🐣关联阅读

上一期:Python 潮流周刊#108:AI 会取代初级开发者吗?

下一期:Python 潮流周刊#110:JIT 编译器两年回顾,AI 智能体工具大爆发