对象的生命周期
程序中的变量必须保存在内存中,而一个变量能够在内存中存在多久,对于编写程序就很重要了。
如果我们想要访问一个变量,但是这个变量已经被销毁掉了,情况就会很尴尬,在c语言中就会出现野指针的问题,程序会直接挂掉。这个时候指针依然指向某个内存地址,但是这个内存地址已经不是给原先的变量使用的了。运气好一点,内存地址还没有分配出去,那么通过指针获得的变量的值还没有变,程序仍然能继续运行。运气不好的话,这段内存被分配给了新的变量,里面的内容已经被改变掉了,程序就不知道跑飞到什么地方去了。不管是上面两种情况中的哪一种,变量的使用都是程序完全无法控制的,这是不应该出现的情况。而且这种问题一旦出现,只能在程序运行时debug,还不好找到。
所以在java或者python这种语言中,一般都会引入生命周期的概念和垃圾回收机制。生命周期是一个抽象的概念,垃圾回收机制则是执行这个思想的具体实体。
生命周期这个概念的核心其实可以用一句话来总结:
一个人真正的死亡是这个世界上最后一个记得你的人将你遗忘。
生命周期说的也是同一件事情。
python中的变量或者对象通过一个叫做引用的东西来找到。引用可以理解为一段内存的别名。一个内存可以有很多个名字。举个例子。
python
lst1 = [1, 2, 3]
lst2 = lst1
python
print(id(lst1))
print(id(lst2))
输出
4507640264
4507640264
在上面这几行代码中,我们让python给我们分配了一段内存用于存储一个列表的对象,然后我们给这个对象起了两个名字,lst1和lst2.那么这个时候我们通过这两个名字都可以操作到这个对象。
python
lst1.append(11)
python
print(lst2)
[1, 2, 3, 11]
python
del lst2[0]
python
lst1
输出
[2, 3, 11]
可以看到,不论我们用那个名字对这个对象进行操作,这段内存都会被改变,从表面上看另一个引用指向的对象也被改变了,但实际上他们指向的是同一段内存。
那么,很自然的,一个对象能在内存中活多久,直接取决于还有没有引用指向它不久好了。一个对象,都叫不上名字,还怎么往程序里面写呢?
python的垃圾回收机制也就是这么做的,它会给每个内存中的对象创建一个引用计数器,比如上面这个列表对象,现在有两个引用指向它,这个对象的引用计数器就是2.接下来,我们把其中的一个引用给删除掉。
python
del lst1
python
lst2
输出
[2, 3, 11]
lst1这个引用被我们删除了,但是我们发现,之前创建的那个列表对象依然存在。现在该对象的引用计数器为1。接下来我们把lst2这个引用也删除掉。
python
del lst2
现在就不用想着从程序角度再去访问这段内存了,因为你已经不知道这个内存的名字了。如果是用C语言这种更接近底层的语言,还可以在知道内存地址的时候直接用指针访问,在python这种高级语言中,为了避免程序员的错误,直接把指针给ban掉了。现在程序员访问不到那个对象,也就是说引用计数器已经为0了,python解释器就会直接把这块内存清理掉,知道需要分配内存的之后再根据需要分配给其他对象。但是虽然可能用到了同一段内存,他们也不是一个对象了。之前的对象已经死掉了。
下面还有两个特殊的例子来分析一下:
- 函数中的局部变量生命周期怎么处理??
python
def func(a, b):
c = 0
print(a + b)
print(c)
在这个函数中,我们声明了3个变量。其中变量c是显式声明的,而变量a,b都是隐式声明的。这三个引用变量出了函数func之后,其生命周期都结束了,也就是说,在函数外部并不存在这三个变量的引用,但是引用a和b是用传引用的方式将变量传进来的,也就意味着a,b指向了在其他地方声明的一段内存,这段内存依然可以用原先的引用进行访问,但是出了函数func之后就不能再用a和b对其进行访问了,只有在原先的引用已经被删除之后,这段内存才会被销毁。
python
l1 = [1, 2, 3]
l2 = ['abc', 'def']
func(l1, l2)
输出
[1, 2, 3, 'abc', 'def']
0
python
print(l1)
print(l2)
输出
[1, 2, 3]
['abc', 'def']
如果传入的参数是直接以对象形式传入的,那么在引用a,b死亡的时候,这段内存就会被销毁
python
func([1, 2], [3, 4])
输出
[1, 2, 3, 4]
0
- 类成员变量的生命周期
python
class C:
def __init__(self, a, b):
self.a = a
self.b = b
self.c = 0
在声明类的时候,这个类还没有对象,那么类的成员变量不在内存中,这时候相当于它还没有出生。
如果在初始化类对象的时候,有两种形式.和例1中给函数传对象一样,如果是一个有引用的对象,则在其所有引用都被删除的时候,这个对象会被销毁,如果传的是没有引用的对象,在类对象生命周期结束的时候,其成员变量指向的内存被销毁。
python
var1 = [1, 2, 3]
var2 = [4, 5, 6]
c1 = C(var1, var2)
python
c1.a
输出
[1, 2, 3]
python
c1.b
输出
[4, 5, 6]
python
del var1
python
c1.a
输出
[1, 2, 3]
python
del c1
python
var2
输出
[4, 5, 6]
python
c2 = C(['a'], ['b'])
python
c2.a
输出
['a']
python
c2.b
输出
['b']
python
del c2
总而言之,要搞清楚对象的生命周期问题,就要分清楚对象占有的内存和对象的引用。一旦一段内存没有被引用指着,这段内存就没有存在的意义了,python解释器会将其回收。