文章目录
前言
学Python有6年了,不算在本科啥也没学的4年,认真搞Python,也有两年了。但是在学习过程中发现有两个问题一直困扰我。
(1)Python的传参与传引用究竟是什么鬼
(2)为什么有时候变量赋值操作会影响原始变量,有时候不会
看了不少资料,解释都千篇一律,基本还是硬记。
后来我觉得有必要了解一下类似列表这类数据结构的底层是什么样的,于是就问了一下GPT内存模型是什么,看完里面恍然大悟。
【注】:虽然看完本篇博客不一定能帮你解决上面的两个疑惑,但是看完本博客再结合网上的千篇一律解释一定能帮你解决这两个疑惑。我自己也写了一篇这样的千篇一律博客Python的传值与传引用及赋值操作详解
看下面内容前带着下面几句话看,或许你仅看本博客就能理解上面两个疑惑了。
(1)Python中万物皆是对象,整数是对象、列表是对象、object都是对象。
(2)变量本质上是对这些对象的引用(或指针)。这意味着变量本身并不存储对象的实际数据,而是存储一个指向对象在内存中位置的引用。
(3)不可变数据(不可变对象)对象不可以变,但是引用(地址、指针、箭头)可以变。
引用=地址=指针=箭头,这种理解是没有问题的。下面开始正题前,先用例子理解这句话:
1 变量=引用=地址=指针=箭头 != 对象(整数、列表、object)
在理解Python中的变量、对象和内存管理时,把“地址”看作是指针或者箭头是非常有帮助的。这种比喻有助于理解变量如何引用对象以及列表如何存储元素的引用。下面我们具体解释一下如何用“指针”和“箭头”来理解这些概念。(下面内容看不懂没关系,看完内存模型就好理解了)
理解“地址”作为指针或箭头
(1) 变量与对象的引用
当我们说一个变量存储一个对象的地址时,可以将其理解为这个变量是一个指针,指向这个对象。或者更形象地,变量是一个带箭头的标签,指向存储对象的内存位置。
示例:
a = 42
这里,a
是一个变量,引用了一个整数对象 42
。用箭头的方式来表示:
a ----> 42
a
是一个箭头(或指针),它指向42
。42
是对象本身,存储在内存的某个位置。
(2) 列表中的引用
对于列表,列表本身是一个对象,它包含多个指向其他对象的指针或箭头。可以将列表看作一个容器,这个容器存储了多个箭头,每个箭头指向列表中的一个元素。
示例:
lst = [1, 2, 3]
我们可以用箭头来表示 lst
列表中的引用关系:
lst ----> [ ref_to_1, ref_to_2, ref_to_3 ]
| | |
V V V
1 2 3
lst
是一个箭头,它指向一个包含多个箭头的列表。- 列表中的每个位置都存储了一个箭头(
ref_to_1
,ref_to_2
,ref_to_3
),这些箭头分别指向1
、2
和3
。
一、整数的内存模型
a = 42
以这个最简单的变量赋值来说明最好不过,这个简单的赋值操作就是计算机编程的最重要概念理解,理解了这个才是真正入门。实际上:
a 存储了指向整数 42 的内存地址(箭头、引用、指针)。
变量 a 和整数 42 是两个不同的实体
这句话解释了Python中变量与对象的关系,尤其是在变量指向不可变对象时。为了深入理解这个概念,让我们一步一步地分解和解释。
变量存的不是对象,而是对象的引用、地址、(指向对象的箭头);不可变对象不可变,但是引用、地址、指向箭头、指针是可以随便变的。
这里 a : 2542304520496(地址:一种映射关系)、一个箭头
变量与对象的关系
在Python中,一切皆对象。变量本质上是对这些对象的引用(或指针)。这意味着变量本身并不存储对象的实际数据,而是存储一个指向对象在内存中位置的引用。
1. 变量和对象的分离
让我们以一个简单的整数赋值操作为例:
a = 42
在这行代码中:
- 对象
42
: 这是一个整数对象,存储在Python的内存空间中。每一个对象都在内存中有一个唯一的地址。 - 变量
a
: 这是一个名称,用来引用存储在内存中的对象42
。
这意味着 a
不是直接存储整数值 42
,而是存储了一个引用,这个引用指向内存中的整数对象 42
。我们可以用一个内存模型来描述这一点:
a: [ ref_to_42 ]
ref_to_42
是指向整数对象 42
的内存地址。
为什么说“变量 a
和整数 42
是两个不同的实体”
-
对象的独立性:
- 整数
42
是一个独立的对象,存储在内存的某个位置。它存在于内存中,具有自己的唯一地址和内容。 - 无论是否有变量指向它,这个对象
42
都是独立存在的。
- 整数
-
变量的引用作用:
- 变量
a
只是一个引用或指针,它保存了整数42
的内存地址。 a
并不包含整数42
的值本身,而是包含一个指向42
的引用。
- 变量
变量和对象在内存中的关系
可以用一个示意图来表示:
Memory:
Address | Value
---------|-------
... | ...
0x1001 | 42
... | ...
Variables:
Name | Reference
------|----------
a | 0x1001
- 在这个示意图中,整数
42
存储在内存地址0x1001
。 - 变量
a
存储了0x1001
这个地址,意味着a
引用的是内存中地址为0x1001
的对象42
。
2. 变量的重新绑定
如果你执行:
b = a
这行代码将 b
也绑定到整数 42
:
Memory:
Address | Value
---------|-------
... | ...
0x1001 | 42
... | ...
Variables:
Name | Reference
------|----------
a | 0x1001
b | 0x1001
- 现在,
a
和b
都引用相同的对象42
。它们保存了相同的内存地址0x1001
。
如果我们之后修改 b
:
b = 43
这会让 b
引用一个新的对象 43
:
Memory:
Address | Value
---------|-------
... | ...
0x1001 | 42
0x1002 | 43
... | ...
Variables:
Name | Reference
------|----------
a | 0x1001
b | 0x1002
- 变量
b
现在引用新创建的整数对象43
,而a
仍然引用42
。 - 这说明变量
a
和b
只是引用,它们可以独立于彼此重新绑定到不同的对象。
3. 不可变对象的特殊性
整数 42
是一个不可变对象,这意味着它的值不能改变。对于不可变对象,任何尝试修改它们的操作都会创建一个新对象。
c