码农高天
尽管编程规范普遍规避动态编译,Python核心库通过前置严格标识符校验并借由eval构建__new__方法,配合_tuplegetter描述符在原生元组上以极简开销实现了兼具属性补全与不可变性的轻量数据结构。
AI-assisted summary of 【python】你听说过namedtuple嘛?会用嘛?知道它实现的原理嘛? — original source. Check the original for full context.
Source published:
Key ideas
- namedtuple 作为动态类工厂函数,在保留原生 tuple 极低开销与不可变性的同时,提供了属性名点号访问和 IDE 智能补全,解决了手写 class 冗余与 dict 易拼写错误的痛点。 (0:00)
- namedtuple 的底层通过继承 tuple 实现,其属性点号访问是基于 operator.itemgetter 与 property 描述符动态绑定到命名空间完成映射的。 (5:00)
- Python 标准库在构造 namedtuple 的 __new__ 初始化逻辑时,采用了常被教程规避的字符串代码拼接与动态编译执行方案,并依靠前置的严格合法性校验保障安全性。 (10:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 2 月 16 日发布。视频简介指出,本期主要探讨相对小众的数据结构 namedtuple,通过介绍其实用特性,帮助开发者在处理杂乱数据时有效减少写出 bug 的概率。
## TL;DR 视频首先对比了手写 class、普通 tuple 和字典在处理轻量坐标数据时的痛点,引出兼具不可变性、IDE 补全友好及点号访问优势的 namedtuple。随后深入标准库源码,揭示了 namedtuple 作为 tuple 子类,如何通过 operator.itemgetter 与 property 巧妙实现属性到元组索引的映射。最后剖析了官方标准库使用字符串拼接与 eval 动态生成 __new__ 方法的“叛逆”实现,并辩证地解释了这种技术选择的合理性与安全性。
## Atmosphere & style 整体氛围轻松诙谐且富有极客探究精神,开场关于 tuple 发音的调侃迅速拉近了与观众的距离。讲解过程层层递进,既有面对工程实际痛点的务实权衡,也有深挖 Python 标准库黑魔法时的豁然开朗。面对常被视作禁忌的 eval 拼接代码,UP 主展现出辩证求真的技术态度,带给观众兼具实战启发与底层源码震撼的硬核观看体验。
## Bulletin - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) namedtuple 作为动态类工厂函数,在保留原生 tuple 极低开销与不可变性的同时,提供了属性名点号访问和 IDE 智能补全,解决了手写 class 冗余与 dict 易拼写错误的痛点。 - [05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) namedtuple 的底层通过继承 tuple 实现,其属性点号访问是基于 operator.itemgetter 与 property 描述符动态绑定到命名空间完成映射的。 - [10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) Python 标准库在构造 namedtuple 的 __new__ 初始化逻辑时,采用了常被教程规避的字符串代码拼接与动态编译执行方案,并依靠前置的严格合法性校验保障安全性。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) 针对诸如 2D 坐标等轻量数据结构,手写完整 class 会引入过多的冗余模板代码,在一次性使用场景下显得小题大做。 - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) 使用原生 tuple 会导致代码中充斥 p[0]、p[1] 等数字索引,严重牺牲代码的可读性与语义表达。 - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) 使用字典 dict 存储轻量数据虽然保留了语义,但缺乏 IDE 属性补全提示,且易因拼写错误随意塞入非法 key 而引入 bug。 - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) namedtuple 结合了各方案优势,只需传入类名与字段列表即可一行完成定义,既支持索引也支持语义化的属性点号访问。 - [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) 作为不可变数据结构,namedtuple 天然具备防篡改特性,非常适合处理网络解包等只读数据的场景。 - [05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) namedtuple 完全基于纯 Python 实现,生成的动态类直接继承自原生 tuple 基类。 - [05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) 支持完整的解包解构赋值语法,并自带 _asdict() 方法方便在字典与命名元组之间相互转换。 - [05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) 属性点号访问机制优先调用 C 扩展 _collections 中的 _tuplegetter,并在缺失时回退到纯 Python lambda 实现。 - [05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) 纯 Python 回退机制使用 operator.itemgetter 提取元组对应索引,再借由 property 伪装为描述符属性读取。 - [10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) 由于元组不可变的本质,实例的构建必须在 __new__ 阶段完成,而非普通的 __init__ 构造器。 - [10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) 标准库通过字符串代码格式化拼接与动态编译来构建 __new__ 方法,打破了常规教程对 eval/exec 的绝对避忌。 - [10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) 官方源码之所以敢用动态字符串拼接,是因为在前序步骤中对所有字段标识符和关键字执行了极其严苛的合法性过滤。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=0s) 轻量级数据结构的痛点与 `namedtuple` 的引入
在日常编写 Python 程序时,开发者常常需要处理一些小巧的数据结构。码农高天在开场调侃道,如果对 “tuple” 这个词的发音有意见可以参考他专门讲发音的视频,这里不再多费口舌。他以 2D 游戏或游戏 AI 中表示坐标点(x, y)为例,梳理了三种传统的数据保存方案及其局限性。第一种是专门为该结构手写一个 `class`,虽然结构最完善且适用于高频复用场景,但若数据只是网络通信中一次性传过来的玩家坐标,“线单独为他写一个class呢有时候稍显荣誉(冗余)”,显得小题大做。第二种是直接用普通的 `tuple` 打包传走,虽然快捷,却会导致代码充斥着类似 `p[0]`、`p[1]` 这种纯下标访问,久而久之不仅让人想不起 0 和 1 各自代表什么含义,还会严重破坏代码的可读性。第三种是用 `dict`(字典)将 x 和 y 作为 key 保存,这种 JavaScript 风格的做法看似折中并保留了语义,但使用者依然要死记 key 的拼写;一旦拼错或者写错,Python 字典的灵活性会导致代码接受了诸如 `xx` 或 `z` 这种非法 key,IDE 也无法给予任何自动补全提示,极易引入潜在 bug。此外,无论是自定义对象还是字典,数据默认都有可能被随意篡改,而在某些场景下不可变性反而是防止 bug 的关键。
针对上述困境,高天引出了本期的主角——`namedtuple`。他明确表示:“你不能说他比其他的方案都优秀但是它确实解决了一些我们上面提到的问题。” 本质上,`namedtuple` 是一个动态构建类的工厂函数。创建它只需要两个核心参数:第一个是新建类的类名,第二个是包含各个字段名称的列表或元组(即 field names),相当于给元组的每个位置赋予了显式的名字。实例化后,它既保留了原生 tuple 的特性,支持方括号下标索引访问(如 `p[0]`),同时也允许通过属性访问语法通过点号取值(如 `p.x`)。与普通元组相比,它拥有明确的语义;与字典相比,IDE 能够精准提供属性名自动补全并在拼写错误时立即报错;与手写完整的类相比,它只需一行代码即可定义完毕,大幅精简了冗余模板代码。
| 方案 | 优点 | 缺点 | |:--- |:--- |:--- | | **手写 class** | 结构完善、功能最丰富、可读性好 | 定义繁琐、模板代码冗余,一次性使用成本高 | | **普通 tuple** | 极其轻量、内存小、速度快、不可变 | 代码充满 `p[0]`、`p[1]` 等数字索引,缺乏可读性 | | **字典 dict** | 支持键值对访问,可读性好于纯 tuple | 允许随意添加非法 key,IDE 无法智能提示,存在拼写致错风险 | | **namedtuple** | 简洁声明、支持点号访问与索引访问、不可变、IDE 补全友好 | 动态构建,灵活性不及普通 class |
```mermaid flowchart TD accTitle: Lightweight Data Structure Approaches accDescr: Comparison of different lightweight data representation methods in Python leading to namedtuple. subgraph Traditional["传统轻量数据方案"] A["手写 class\n代码完善但定义冗长"] B["普通 tuple\n轻量但充斥 p[0], p[1] 索引"] C["字典 dict\n支持键值访问但无 IDE 补全且易拼错"] end subgraph Solution["优化解决方案"] D["namedtuple\n动态生成轻量类"] D -->|"特性 1"| E["支持属性点号访问 p.x 与 IDE 补全"] D -->|"特性 2"| F["兼容元组索引访问 p[0] 及解构赋值"] D -->|"特性 3"| G["保持元组不可变性 (immutable)"] end ```
---
[05:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=300s) 源码剖析:继承 `tuple` 与 `_tuplegetter` 属性黑魔法
高天指出,虽然 Python 后来推出了功能更强大的 `dataclass`,但 `namedtuple` 的底层实现非常耐人寻味。其源码完全由纯 Python 编写,没有复杂的底层 C 扩展羁绊,却蕴含了许多巧妙的“黑魔法”。在标准库注释与文档中,`namedtuple` 还支持诸多实用特性:它可以像普通元组一样直接进行解包解构(如 `x, y = p`),自带 `_asdict()` 方法将其直接转成字典,也能直接通过字典解包实例化对象。随后高天切入源码的类定义逻辑:`namedtuple` 动态创建类时,其元类调用将 `tuple` 作为基类传入(`base class` 为 `tuple`),这意味着生成的类本质上就是 `tuple` 的子类,天然继承了原生元组的全部能力与不可变特性。
那么核心机制在于:它是如何做到访问属性 `p.x` 时,自动映射拿到索引为 0 的元素的?高天带着大家逐行解析其类命名空间(class namespace)中的逻辑。系统会遍历传入的每一个字段名称(如 `x` 和 `y`),并在命名空间中将该属性名绑定到一个由 `_tuplegetter` 生成的属性上。对于 `_tuplegetter`,源码先尝试从 C 语言扩展 `_collections` 中导入,若导入失败则回退(fallback)到一段仅有一行的纯 Python 实现:“这个 tuplegetter 是什么呢大家看这里,首先他从这个下滑线 collections 里面尝试 import 一下,那看了之前一期视频的人呢大概就能猜到这个呢就是他 C 的实现如果有 C 的实现的话我用 C 的实现,但它的功能我们可以从它的 fallback 也就是在这个 ImportError 产生的情况下它的 Python 实现来猜一下”。这行纯 Python 代码非常精巧,它是一个接收 `index` 和 `doc` 参数的 lambda 函数,其核心调用了内置的 `property` 和 `operator.itemgetter`:
- **`operator.itemgetter(index)`**:返回一个可调用对象,等价于一个接收 `self` 并执行 `return self[index]` 的函数,作用是从元组中提取第 `index` 个元素。 - **`property(...)`**:平时常作为装饰器使用,本质上是一个描述符构造器。当把它直接包裹在提取函数外部时,它将原本需要带括号调用的函数伪装成了一个普通的属性读取行为。 - **绑定到命名空间**:通过 `class_namespace[name] = _tuplegetter(index, doc)`,属性 `x` 就被绑定成了一个由 `property` 包装的描述符。当用户执行 `p.x` 读取该属性时,Python 底层便会自动调用该函数,执行 `itemgetter` 从元组的第 0 位抓取数据。
---
[10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) 争议实现:字符串拼接与 `eval` 构造 `__new__`
在深入理解了属性绑定的本质后,高天进一步解析了实例创建阶段的细节。由于元组是不可变数据结构(immutable),其属性和数据初始化工作不能在常规的 `__init__` 中执行,而必须在 `__new__` 阶段完成。令人意想不到的是,`namedtuple` 官方源码构建 `__new__` 方法的方式显得极度“叛逆”——它竟然是通过 Python 字符串格式化拼接出函数定义的代码文本,然后直接调用 `eval`(或 `exec` 配合环境)来动态编译生成的。很多 Python 入门书籍和教程都会反复告诫开发者“绝对不要随便使用 eval,不要用字符串拼接代码”,然而 Python 核心标准库本身却堂而皇之使用了这种写法。
面对这种实现,高天给出了辩证的看待方式:“你看很多 Python 书都会告诉你绝对不要这么写代码,这就说明万事万物都要去辩证的看有些技术确实有它的缺陷但也不是不能使用。” 官方通过字符串拼接动态构造了一个参数列表与外部字段完全一一对应的 `__new__` 函数,将外部传参打包后直接交给底层 `tuple.__new__`。之所以敢这样用,是因为源码在前序逻辑中做足了严格的标识符合法性检查与关键字过滤,彻底封堵了代码注入的风险。在视频结尾,高天总结道,`namedtuple` 虽然在各大社区中探讨较少、相对小众,但它在处理临时结构体、网络通讯数据包解包以及轻量级只读数据处理时极为实用且能有效规避手误 bug,非常推荐开发者在合适场景下大胆尝试。
## Notable quotes > [10:00](https://www.youtube.com/watch?v=gSx0KYjZcHs&t=600s) 万事万物都要去辩证的看有些技术确实有它的缺陷但也不是不能使用。 — 码农高天
## Mentioned Python, namedtuple, tuple, dict, dataclass, operator.itemgetter, property, _tuplegetter, eval, exec, 码农高天
## Video description 这期视频我们讲一个比较小众的数据结构namedtuple。这个内容B站上做过的人很少,但是它有一些挺实用的feature。在我们处理一些比较杂的数据的时候,可以有效地降低我们写bug的几率。了解完试用一下才知道好不好用~
[Source](https://www.youtube.com/watch?v=gSx0KYjZcHs)