python-内存管理机制
概述
在 Python 中,一切皆对象,变量存储的是对象的引用。因此研究 Python 的内存模型就是研究 Python 中对象的引用机制的问题。
在介绍 Python 的内存模型之前,先介绍一下一些方便我们做测试的函数和运算
id(object):返回对象的内存地址
==:判断等号两端的值是否相同
is:判断两端的内存地址是否相同
内存模型
先运行下面的代码
x = 1
print(id(x))
x = 2
print(id(x))
可以发现两次 print(x)
的值其实不一样。这说明了 python 中,不是先给 x
开辟内存空间,然后再将值放进去,而是将要进行赋值的值这个对象的地址给 x
。
运行代码
a = 1
b = a
print(id(a))
print(id(b))
可以发现两次 print
的结果是一样的,因此这个 =
应该是传递了引用
Python 的赋值语句不复制对象,而是创建目标和对象的绑定关系。
python 中还有可变对象和不可变对象的区别:
a = 1
l1 = [1, 2]
b = a
l2 = l1
a = 2
l1[0] = 2
上面代码执行完后,b=2,l2=[2,2]
,即 a
的改变没有影响到 b
,而 l1
的改变影响到了 l2
。Python 中 int, float
这些属于不可变的类型,而 list, tuple
这些属于可变的类型。
我的理解是 Python 中的变量类似于 c 中的常指针,他不能修改他指向的地址中的值,但是它可以指向不同的地址
因此从我这个理解出发的话,列表之所以可变,是因为列表中的每个元素其实是一个指针,因此使用l[0]
时,我们修改了该指针指向的值,因此其他引用了该列表的元素也会做出修改
有时候,对于列表这样的可变类型,我们不希望同步的修改数据。对于自身可变,或包含可变项的集合,有时要生成副本用于改变操作,而不必改变原始对象 [^1]。此时,可以使用拷贝函数,浅层拷贝函数 copy
只复制一层,而深层拷贝函数 deepcopy
则是递归的复制
垃圾回收
每个对象在存在一个引用计数,当引用计数为 0 时会进行垃圾回收。
引用计数增加的情况:
- 一个对象被分配给一个新的名字(例如:
a=[1,2]
) - 将其放入一个容器中(例如:
c.append(a)
)
引用计数减少的情况:
- 使用
del
语句对对象别名显式的销毁 - 对象所在的容器被销毁或从容器中删除对象
- 引用超出作用域或被重新赋值
引用计数能解决大多数垃圾回收的问题,但是会遇到循环引用的问题。为了解决标记清除和提高效率,可以采用“标记清除”和“分代回收”的方法。其中,前者用于解决循环引用的问题,后者则基于对象的生命周期长短将对象分为不同的代,以提高垃圾回收的效率。[^2]
可以通过 sys.getrefcount
方法查看引用计数
from sys import getrefcount
a = [1, 2, 3]
b = a
del b
print(getrefcount(a))
References
[^1]:copy-浅层和深层赋值操作-Python 3.12.0 文档
[^2]:没白熬夜,终于把Python的内存管理机制搞明白了-知乎