码农高天
对象的不可变性并非源于Python虚拟机的类型约束,而是C底层封死所有纯Python修改接口的结果,在底层内存中所有对象本质上均可被修改。
AI-assisted summary of 【python】mutable和immutable其实根本没区别?带你了解这个概念背后你没思考过的东西 — original source. Check the original for full context.
Source published:
Key ideas
- Python 虚拟机层面并不存在 mutable 与 immutable 的类型区分,两者纯属人类为了方便分类而人为创造的概念,运行时对它们的操作机制完全一致。 (0:00)
- 常见的教学案例混淆了局部变量重新赋值与对象原地修改,且 Python 无论传递可变还是不可变对象,传参时都仅传递共享引用,绝不存在不可变对象的额外复制机制。 (1:45)
- 不可变性并非由类型标签赋予,而是类型底层在实现时封死了所有通过纯 Python 修改其内存数据的交互接口;在 C 扩展视角下,所有 Python 对象本质上都是可被直接修改的内存块。 (5:00)
Detailed notes
## Background 该视频由频道“码农高天”于 2024 年 2 月 20 日发布在 YouTube 平台。视频简介指出,本期主要探讨 Python 中的 mutable(可变)与 immutable(不可变)对象,揭示 Python 底层对二者的操作完全一致、根本无法区分这一事实,并带领观众思考该概念背后更深层的内容。
## TL;DR 视频深入剖析了 Python 中“可变”与“不可变”对象的本质,指出这两个概念只是人类便于分类归纳的标签,Python 解释器本身既无法区分也没有对它们做差异化处理。作者逐一驳斥了教程中常见的“障眼法”反例,阐明运算符行为差异源于底层魔法方法的具体实现,且函数传参时始终传递引用而绝非复制对象。所谓不可变性,其实是 CPython 在底层用 C 语言封死了所有纯 Python 可修改内存的路径,一旦进入 C 扩展层面则万物皆可变;因此开发者应把这一概念当作沟通工具,遇到困扰时不妨像 Python 解释器一样对二者一视同仁。
## Atmosphere & style 整期视频呈现出一种直击本质、快节奏且辛辣犀利的技术辟谣风格。码农高天以极具穿透力的视角拆解常见误区,言辞直率不留情面,在痛斥某些错误教程“胡说八道”时带有鲜明的情绪起伏与说服力。伴随着层层递进的代码与底层机制推导,观看体验如同拨开层层概念迷雾,让人在恍然大悟之余感受到一种通透干脆的底层思维乐趣。
## Bulletin - [00:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=0s) Python 虚拟机层面并不存在 mutable 与 immutable 的类型区分,两者纯属人类为了方便分类而人为创造的概念,运行时对它们的操作机制完全一致。 - [01:45](https://www.youtube.com/watch?v=39vaZ93ICko&t=105s) 常见的教学案例混淆了局部变量重新赋值与对象原地修改,且 Python 无论传递可变还是不可变对象,传参时都仅传递共享引用,绝不存在不可变对象的额外复制机制。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 不可变性并非由类型标签赋予,而是类型底层在实现时封死了所有通过纯 Python 修改其内存数据的交互接口;在 C 扩展视角下,所有 Python 对象本质上都是可被直接修改的内存块。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=0s) Mutable 和 immutable 并非计算机底层或 Python 语言原生理解的硬性概念,而是人类对对象行为归纳出的抽象分类标签。 - [00:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=0s) Python 语言层面无法量化、抓取或通过纯代码检验所谓的“内部状态”,运行时对所有对象采取完全一致的调用机制。 - [01:45](https://www.youtube.com/watch?v=39vaZ93ICko&t=105s) 教材中常用“函数内修改列表与修改整数”来对比可变性属于障眼法,混淆了局部变量重新赋值与对象内部方法的调用。 - [01:45](https://www.youtube.com/watch?v=39vaZ93ICko&t=105s) `+=` 运算符在 list 和 int 上的表现分歧,源于各自 `__iadd__` 方法的实现逻辑不同,而不是解释器识别了可变/不可变标签。 - [01:45](https://www.youtube.com/watch?v=39vaZ93ICko&t=105s) “传递 immutable 对象会复制一份”的说法完全错误,Python 传参始终传递对象的内存引用,通过 `id()` 可验证形参与实参完全一致。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 元组等结构之所以不可变,是因为其底层的 `__setitem__` 等接口直接禁止了写入,而非 Python 运行时主动约束了元组的属性。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 不可变对象的准确定义是“无法通过纯 Python 手段修改其内部数据”,其实质是通过底层实现把所有纯 Python 的修改入口全部封死。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 真正的不可变性必须依赖 C 语言底层封装实现,纯 Python 代码无法构造出完全无法被篡改的绝对不可变对象。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 在 CPython 底层 C 扩展开发视角下,所有对象都只是普通内存块,没有任何对象是不可变的,C 指针可任意篡改其数据。 - [05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) Mutable 和 immutable 仅应作为辅助思考的工具标签,当该概念引发逻辑困扰时,应回归 Python 底层行为一致的视角来理解程序。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=0s) Python 本身根本不区分 Mutable 与 Immutable
在 Python 学习过程中,`mutable`(可变)和 `immutable`(不可变)是经常被提及的概念,但许多人对其理解十分模糊。码农高天直言,如果你不知道它们之间到底有什么区别,“那就对了,因为他们俩本质上没有区别”。尽管书籍和官方文档中都充斥着对这两个术语的定义,但这并不意味着它们是计算机或 Python 语言原生理解的底层概念。这两个词纯粹是人类为了对不同的对象(object)进行归纳分类而人为创造出来的。
```mermaid flowchart TD accTitle: Python 处理对象与人类认知的差异 accDescr: 展示人类概念层与 Python 底层调用机制的不同处理路径
subgraph Human["人类视角(抽象分类)"] M["Mutable 对象(如 list)"] IM["Immutable 对象(如 int、tuple)"] end
subgraph Runtime["Python 虚拟机视角(统一处理)"] Call["直接调用对象底层对应的方法<br/>(如 __iadd__、__setitem__)"] Mem["内存中的 C 数据结构"] end
M -->|"无视标签"| Call IM -->|"无视标签"| Call Call --> Mem ```
Python 语言层面既不知道什么东西是 mutable,也不知道什么东西是 immutable,开发者也根本写不出一套纯 Python 代码来直接检验一个类是可变还是不可变。虽然官方或常见教程常将 immutable 对象定义为“内部状态(internal state)不能被改变”,但这种表述细究起来站不住脚:在 Python 中程序根本无法量化或抓取所谓的“内部状态”,它只是一个人脑容易理解、程序却无法解析的概念。码农高天强调:“Python是根本不会去分辨一个object是mutable还是immutable的,Python对于mutable和immutable的object行为是完全一致的。”
- 人类分类与语言底层:mutable 和 immutable 不是 Python 数据结构自带的语法标记,而是具体类的实现方式在客观上决定了它表现出来的特性。 - “内部状态”的模糊性:程序无法真正提取或定义一个对象的内部状态边界,因此该定义缺乏严格的操作性。
---
[01:45](https://www.youtube.com/watch?v=39vaZ93ICko&t=105s) 常见反例辨析:别用“障眼法”解释概念
针对“可变与不可变对象行为不同”的反驳,码农高天拆解了几个常见的典型例子。最粗浅的教学案例通常是:定义一个空列表 `L`(mutable)和一个整数 `A`(immutable),分别传入函数;在列表函数内部执行 `list.append(1)`,外部列表发生了变化;而在整数函数内部写 `int = 1`,外部变量没有变化。这种对比被码农高天斥为“睁着眼睛说瞎话”和经典的障眼法——两个函数调用的操作完全不同,一个是调用方法追加元素,另一个仅仅是局部变量重新赋值,行为当然不一样;若在函数内部把列表重新赋值为 `list = [1]`,外部同样毫无变化。
更为隐蔽的例子则是使用加等于运算符(`+=`):对一个 `list` 进行 `list += [1]`,对象在原地被修改了;而对整数 `i` 进行 `i += 1`,它并没有原地修改。表面上看两边写法一致,但背后的实质是二者 `+=` 对应魔法方法的底层实现截然不同。整数的 `+=` 实现返回了一个全新的 integer 对象,而列表的 `+=` 则是就地修改原本的列表并返回自身引用。针对这种混淆,码农高天反问:“难道因为我实现的这个类,它的加等于的行为和integer是一样的,它就是immutable了吗?不是这样的。”Python 在执行这些语句时,根本没有去识别类型标签,仅仅是机械、直接地调用了它们各自的实现。
针对传参机制的误解,部分教程声称“传递 mutable 时传的是同一个对象,而传递 immutable 时会复制一份对象”,码农高天直接给出四个字的评价:“胡说八道”。无论对象是可变还是不可变,函数传参时传递的都是同一个对象的内存地址。只要用 `id()` 函数分别打印外部传入的字符串(immutable)和函数内部形参的 `id`,就会发现数值毫无差异,完全不存在传参时额外复制的机制。
- 赋值与原地修改的混淆:局部变量重新绑定(`var =...`)与对象内部方法调用(`.append()`)不可同日而语。 - 运算符背后是方法实现:`+=` 的行为分歧源于类型内部 `__iadd__` 方法的实现逻辑,而不是类型被打上了“可变/不可变”属性。 - 传参实质:Python 始终采用共享对象引用的传递方式,传参阶段绝不会对 immutable 对象进行拷贝。
---
[05:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=300s) 底层实现、C 扩展与该概念的正确使用姿势
列表允许索引赋值(如 `L[0] = 1`)而元组(`tuple`)抛出异常,其根源同样不是 Python 预先识别了元组的不可变性,而是因为元组内部的 `__setitem__` 实现直接禁止了这项操作。所谓的 mutable 与 immutable,其核心定义必须加上严格的限定条件——“能不能通过纯 Python 来修改一个 object 里面的数据”。
在 CPython 虚拟机架构下,开发者在纯 Python 层面能调用的接口和手段是有限的。如果一个数据结构在底层用 C 语言实现时,把所有纯 Python 能够修改该结构内存的入口全部封死,它在 Python 视角下就变成了 immutable。码农高天强调其中的因果关系反差:“不是我实现的时候给这个数据贴个标签说这是immutable然后python看到这个标签就不改它了,不是这样的;是所有python有可能和这个数据交互的方式,我都在实现上,让他不要修改这个数据,然后他才成为了一个immutable的。”
这就引出了一个底层事实:所谓的“真不可变”只能依托于 C 语言层面的封装来实现,纯 Python 代码无法真正打造出一个有意义的完全不可变对象(虽然可以通过属性拦截等黑魔法增加篡改难度,但无法彻底封死修改途径)。而一旦脱离纯 Python 环境进入底层 C 扩展开发,由于所有对象本质上都是内存中可读写的数据块,整个 Python 数据体系将“没有任何东西是 immutable 的”,开发者可以用 C 指针改写任意想改的内存。
最后,码农高天总结道,厘清这套机制并不是要全盘否定 mutable 和 immutable 这对概念的价值。它们作为人类思考的抽象标签,能帮助程序员用一个词概括一组预期的行为特性。但大家不必对其产生教条式的认知负担:“当这个概念对你理解有帮助的时候使用它,当这个概念开始对你造成困扰的时候,你就可以像python一样认为他们俩是一样的。”
- 封死路径而非贴标签:不可变性是通过在底层杜绝所有纯 Python 交互修改路径来实现的,而不是运行时的规则约束。 - C 语言视角的穿透性:在 C 扩展层面,所有 Python 对象均直接映射为可变内存,不可变的神话在底层不复存在。 - 概念的工具属性:将可变性视为方便人类沟通的代码约定标签,当其产生逻辑困扰时,应回归 Python 底层行为一致的视角。
## Notable quotes > [00:00](https://www.youtube.com/watch?v=39vaZ93ICko&t=0s) Python是根本不会去分辨一个object是mutable还是immutable的,Python对于mutable和immutable的object行为是完全一致的。 — 码农高天
## Mentioned Python, CPython, C语言, 码农高天
## Video description 这期视频讲一下mutable和immutable,也就是可变对象和不可变对象。很多人可能压根没意识到,python对于mutable和immutable的操作是完全一致的,也就是python根本无法区分一个对象是mutable还是immutable。那这个概念背后到底有着什么值得思考的内容呢?
[Source](https://www.youtube.com/watch?v=39vaZ93ICko)