内存管理¶
对象¶
在Python中,变量只是一个名字,并不意味着内存地址。也就是说对于a=12345
这样一个表达式来说,堆上有一块内存区域存储了变量的名称a
,它是一个字符串;还有一块区域存储了12345
这个实际的对象;还有一块区域我们称为名字空间,它是一个字典,存储了a
和12345
建立的映射关系。
对于这个实际的对象,它又分为两部分,头部存储元数据,包括它的引用计数,以及它是什么类型的;而数据部分对于固定长度的,则直接是它的数据,对于可变长度的,则首先有一部分描述该对象有多少个元素。
引用¶
当我们把一个变量赋值给另一个变量,传递的是实际对象的引用,python总是按引用传递的,包括函数的参数也是:
而像Go、C这样的语言,总是会按值传递,也就是说不管是把一个变量赋值给另一个变量,还是函数传参,都会把这个实际的对象12345
复制一份。
对于del a
这样的语句,只是把a
和12345
这个映射关系解除,并把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