Python与JavaScript 内存存储与深浅拷贝
·
Python 与 JavaScript 变量内存存储机制
一、核心底层本质差异(最关键)
- Python:万物皆对象
- Python 中所有数据都是堆中的对象(数字、字符串、布尔、 元组、列表、函数全部是对象)。
- 变量没有类型、不存数据,只是对象的引用标签(内存地址指针)
- 真正的数据、类型、内存地址全部保存在堆对象中
- 核心区分:可变对象、不可变对象
- JavaScript:原始值 + 引用对象 双模型
- JS 采用两套完全独立的存储规则:
- 基本原始(简单数据)类型(Number、String、Boolean、Null、Undefined、Symbol、BigInt):值直接存在栈内存
- 引用(复杂)数据类型(Object、Array、Function):数据存在堆内存,栈中仅存堆的内存地址
- 变量直接存储「值」或「地址」,变量本身有明确存储内容
二、内存分区与基础赋值逻辑对比
- 内存分区通用规则
- 所有高级语言内存分为两块:
- 栈内存:空间小、读写快、自动释放,存放变量、基础值、引用地址
- 堆内存:空间大、持久存储,需要垃圾回收,存放复杂对象数据
- Python 赋值底层逻辑
- Python 没有任何数据存在栈中,栈永远只存指针:
- a = 10:堆内存创建整数对象 10
- 栈内存变量 a 存储该对象的内存地址
- b = a:b 和 a 指向同一个堆对象
- 核心特点:赋值永远是「引用拷贝」,不拷贝数据本身。
- JavaScript 赋值底层逻辑
- 原始(简单)类型
栈内存直接存储数值 10,赋值是完整的值拷贝,两个变量完全独立。let a = 10; let b = a; a = 20; // b 仍然是 10 - 引用(复杂)类型
栈中存储堆数组的地址,赋值拷贝地址,多个变量共享堆数据。let a = [1,2]; let b = a; a.push(3); // b 同步变为 [1,2,3]
- 原始(简单)类型
三、Python可变 / 不可变数据
- 不可变数据(整数、浮点数、字符串、布尔、元组、复数、冰冻集合)
- 不可变对象无法修改原有堆数据,重新赋值会新建对象、更换引用。
a = 100 b = a a = 200 # 新建200对象,a更换指向,b不变 print(b) # 100
- 不可变对象无法修改原有堆数据,重新赋值会新建对象、更换引用。
- 可变数据(列表、字典、 普通集合)
- Python List、Dict 为堆中可变对象:
- 修改数据是直接修改堆内存原数据
- 所有指向该对象的变量,数据会同步变化
- Python List、Dict 为堆中可变对象:
四、函数参数传递机制
- Python:统一「传引用」
- 传不可变对象:函数内重新赋值会新建对象,外部数据不变
- 传可变对象:函数内修改原堆数据,外部同步改变
- JavaScript:统一「按值传递」
- 原始类型:拷贝真实数值,内外完全隔离
- 引用类型:拷贝堆地址,可通过地址修改堆原数据
五、Python 专属内存优化机制(重点)
Python 为了减少频繁创建、销毁对象的性能开销,设计了两大缓存机制:小整数池、字符串驻留,这是 JS 不具备的显式机制。
-
小整数池(整数缓存机制)
- 原理
Python 解释器启动时,提前在堆中预创建 -5 ~ 256 所有整数对象,常驻内存、永不销毁。
开发中最常用的小整数全部复用缓存对象,无需重复开辟内存,极致优化性能。 - 存储位置
Small Integer Cache - 代码验证:
# 在交互环境测试 a = 100 b = 100 print(id(a) == id(b)) # True,共用同一个对象 c = 300 d = 300 print(id(c) == id(d)) # False,超出缓存池,新建对象 - 补充说明
脚本文件运行时会触发常量折叠编译优化,大于 256 的相同整数也可能地址一致,这不是小整数池机制,属于编译优化,和运行时缓存无关
- 原理
-
字符串驻留(String Interning)
- 原理
字符串是不可变对象,Python 将高频重复的规则化字符串缓存到全局驻留池,相同内容的字符串只存储一份,实现内存复用、加速比对。 - 自动驻留规则
只有满足以下条件,才会自动进入驻留池:
仅包含:字母、数字、下划线(合法标识符格式)
无空格、无特殊符号、无中文 - 存储位置
Python维护一张全局表,intern table - 代码验证
# 自动驻留 s1 = "user_123" s2 = "user_123" print(id(s1) == id(s2)) # True # 不满足规则,不驻留 s3 = "hello world" s4 = "hello world" print(id(s3) == id(s4)) # False - 手动强制驻留
通过 sys.intern() 可强制任意字符串加入缓存池。
sys.intern(s) 会把字符串放入全局字符串驻留池,返回驻留后的字符串对象,保证后续用 intern 获取同一字面量时拿到同一个 id。
但不会自动驻留普通字面量创建的长字符串。import sys a = sys.intern("hello world") b = sys.intern("hello world") print(id(a) == id(b)) # True
- 原理
-
常量池
- 概述
常量池就是编译器提前把程序中出现的常量保存起来,运行时直接引用,而不是每次都重新创建。 - 存储位置
它保存在 Code Object(代码对象) 中 - 哪些对象可以放进常量池
整数、浮点数、字符串、元组 - 判断标准
- 编译期可确定的不可变常量
- 必须同时满足:
✅ 不可变(Immutable)
✅ 编译期可确定(Compile-time Constant)
- 作用
避免重复创建不可变对象 - 常量池总结
对象 不可变 编译期可确定 能否进入 co_consts123✅ ✅ ✅ 3.14✅ ✅ ✅ "hello"✅ ✅ ✅ (1, 2, 3)✅ ✅ ✅ (x,)✅ ❌ ❌ tuple([1,2,3])✅ ❌ ❌ "ab" * 3(CPython 会常量折叠)✅ ✅ ✅ "ab" * n✅ ❌ ❌ [1,2,3]❌ ✅ ❌
- 概述
-
小整数池、字符串驻留、常量池 总结
| 机制 | 是否编译期 | 作用对象 | 生命周期 |
|---|---|---|---|
常量池(co_consts) |
是 | 编译期常量(数字、字符串、元组等) | 属于某个代码对象(函数或模块) |
| 小整数缓存 | 否(解释器启动时建立) | 通常是 -5 到 256 的整数 |
整个解释器进程 |
| 字符串驻留(intern) | 否(可自动或手动) | 字符串 | 整个解释器进程 |
六、JS 与 Python 核心差异总结表
| 对比维度 | Python | JavaScript |
|---|---|---|
| 存储核心 | 万物皆堆对象,变量只存引用地址 | 原始值存栈,对象存堆,双存储模型 |
| 赋值逻辑 | 永远拷贝对象引用 | 原始值拷贝值,引用类型拷贝地址 |
| 整数优化 | 拥有 -5~256 小整数缓存池 | 无整数缓存机制 |
| 字符串优化 | 严格规则的自动驻留池 + 手动驻留 | V8 隐式驻留,无公开固定规则 |
| 参数传递 | 传对象引用 | 统一按栈内值传递 |
| 垃圾回收 | 引用计数 + 分代 GC | 标记清除算法 |
| 数据区分 | 可变 / 不可变对象 | 原始类型 / 引用类型 |
七、总结
- Python:没有变量存数据,所有数据都是堆对象,一切赋值都是引用传递,靠小整数池和字符串驻留极致优化内存性能;
- JavaScript:基础值栈上直接存储,复杂数据堆存储,分层管理内存,无显式常量缓存池机制。
Python与JavaScript 赋值、深浅拷贝
一、Python
-
可变对象和不可变类型划分
不可变类型:
数值、字符串、元组.
可变类型:
列表、字典、集合
总结:
在不改变地址值的情况下,内容可以改就是可变类型,不可以改就是不可变类型 -
地址赋值、浅拷贝、深拷贝规则
纯不可变类型:
普通赋值、浅拷贝、深拷贝, 地址都不变
原值修改不影响拷贝后的值可变类型:
普通赋值, 地址不变; 浅拷贝、深拷贝, 地址会变
普通赋值、浅拷贝, 原值修改 影响 拷贝后的值; 深拷贝,原值修改不影响拷贝后的值深拷贝内部逻辑:
遍历要拷贝对象的每一层;
若当前层是可变对象(list / dict / 自定义实例):开辟新堆内存,完整复制一份;
若当前层是纯不可变对象(tuple / int / str):为了性能优化,直接返回原对象引用,不新建内存。 -
注意点
# 不可变类型 值相同 内存中只存一份 a = 3 b = 3 print(id(a)) # 4545460504 print(id(b)) # 4545460504 print(id(3)) # 4545460504 # 如果深拷贝遇到纯不可变对象(tuple/int/str/float),不会新建内存,直接复用原对象地址 a_tuple = ((1, 2, 3),) b_tuple = copy.deepcopy(a_tuple) print(id(a_tuple) == id(b_tuple)) # True # 深拷贝拷贝非纯不可以变类型地址会变 a_tuple = ([[1,2,3],[4,5,6]],) b_tuple = copy.deepcopy(a_tuple) print(id(a_tuple) == id(b_tuple)) # False print(id(a_tuple[0]) == id(b_tuple[0])) # False print(id(a_tuple[0][0]) == id(b_tuple[0][0])) # False print(id(a_tuple[0][0][0]) == id(b_tuple[0][0][0])) # True deepcopy对不可变对象的复用策略导致的
二、JavaScript
-
浅拷贝方式
- 扩展运算符 …(最常用)
- Object.assign()
-
深拷贝方式
- Lodash 深拷贝 _.cloneDeep
-
JS中主要是针对引用(复杂)数据类型
方式 新外层对象 第一层数据独立 嵌套对象独立 底层原理 直接赋值 ❌ 无新对象 ❌ 共享引用 ❌ 共享引用 复制内存地址,共用堆对象 浅拷贝 ✅ 新建外层 ✅ 基本类型独立 ❌ 嵌套共用引用 只递归一层,深层仅复制地址 深拷贝 ✅ 全新外层 ✅ 全部层级独立 ✅ 所有嵌套全新 递归创建每一层对象,完全隔离
更多推荐
所有评论(0)