跳转至

内存管理

对象

在Python中,变量只是一个名字,并不意味着内存地址。也就是说对于a=12345这样一个表达式来说,堆上有一块内存区域存储了变量的名称a,它是一个字符串;还有一块区域存储了12345这个实际的对象;还有一块区域我们称为名字空间,它是一个字典,存储了a12345建立的映射关系。

对于这个实际的对象,它又分为两部分,头部存储元数据,包括它的引用计数,以及它是什么类型的;而数据部分对于固定长度的,则直接是它的数据,对于可变长度的,则首先有一部分描述该对象有多少个元素。

引用

当我们把一个变量赋值给另一个变量,传递的是实际对象的引用,python总是按引用传递的,包括函数的参数也是:

In [1]: a=12345
In [2]: def test(x):
   ...:     return x is a
In [3]: test(a)
Out[3]: True
而像Go、C这样的语言,总是会按值传递,也就是说不管是把一个变量赋值给另一个变量,还是函数传参,都会把这个实际的对象12345复制一份。

对于del a这样的语句,只是把a12345这个映射关系解除,并把12345的引用计数减一,当12345的引用计数为0时它会自动被垃圾回收器回收。使用sys.getrefcount(a)这个函数会得到a对应的实际对象的引用计数,但它总会比实际的计数多1,因为它本身也传入了a为参数。

弱引用

名字和对象之间的强引用关系影响了对象的生命周期,但有时我们希望能以"路人甲"的身份进行一些观察、日志、监测等,既能够观察到目标的状态,又不会干扰到其被回收。这时候我们就需要用弱引用。

In [1]: import weakref
In [2]: class X:
   ...:     def __del__(self):
   ...:         print("in del real obj")
   ...:     def test(self):
   ...:         print("X.test")
In [3]: o = X()             # 创建实例
In [4]: w = weakref.ref(o)  # 为该实例创建弱引用
In [5]: w() is o            # 通过弱引用引用目标
Out[5]: True
In [6]: w().test()          # 基于弱引用进行方法调用
X.test
In [7]: del o               # 弱引用不影响目标被回收
in del real obj
In [8]: w() is None         # 目标被回收后弱引用不再引用
Out[8]: True
还有一种代理的方式,它看上去会和原本的调用方式更类似一些,它内部会做一些包装处理,但通过它很难找到原本的对象。
In [10]: o = X()
In [11]: p = weakref.proxy(o)   # 为该实例创建代理
In [12]: p.test()               # 通过代理可直接调用实例的方法
X.test
In [13]: p is o                 # 但代理不是原对象
Out[13]: False
In [14]: p.test.__self__ is o   # 可以曲折的找到原对象
Out[14]: True

复制

有时候,对于可变对象,我们更喜欢选择值传递的方式,因为克隆体无论怎么折腾,都不会影响到原来的对象。这时候就需要复制对象,又分为浅拷贝(shallow copy)和深度拷贝(deep copy)两种,前者只复制对象自身,不包括它引用的其他对象,后者则递归复制所有引用目标。

In [1]: class X: pass
In [2]: x = X()
In [3]: x.data = [1,2,3]
In [4]: import copy
In [5]: x1 = copy.copy(x)
In [6]: x2 = copy.deepcopy(x)
In [7]: x1 is x
Out[7]: False
In [8]: x2 is x
Out[8]: False
In [9]: x1.data is x.data       # 浅拷贝不包括所引用的目标
Out[9]: True
In [10]: x2.data is x.data      # 深拷贝连同引用目标也被复制
Out[10]: False
此外,也可以通过序列化和反序列化的方式,达到深度拷贝的目的。

内存分配

垃圾回收