基于Python对象引用、可变性和垃圾回收详解

  • Post category:Python

基于Python对象引用、可变性和垃圾回收详解

Python对象引用

在Python中,我们可以用一个变量来指向一个对象,并可以通过改变这个变量的值来改变这个对象。这里的“指向”是指变量存储的是对象的“引用”,也就是对象在内存中的地址。

例如:

a = [1, 2, 3]
b = a

在这个例子中,我们创建了一个列表对象[1, 2, 3],然后用变量a来指向它,再用变量b也指向它。这样,a和b都“引用”了同一个对象。因此,修改a的值,也会改变b的值,反之亦然。如下:

a[0] = 4
print(a) # 输出 [4, 2, 3]
print(b) # 输出 [4, 2, 3]

Python对象的可变性和不可变性

在Python中,有些对象是“可变”的,也就是说,它们的值是可以改变的;有些对象则是“不可变”的,也就是说,它们的值一旦确定就不可改变。

例如,字符串、元组和数值类型等都是不可变的(即“不可变类型”),而列表和字典等则是可变的(即“可变类型”)。如下:

# 不可变类型示例
a = "abc"
b = a
a = "xyz"
print(a) # 输出 "xyz"
print(b) # 输出 "abc"

# 可变类型示例
a = [1, 2, 3]
b = a
a[0] = 4
print(a) # 输出 [4, 2, 3]
print(b) # 输出 [4, 2, 3]

Python的垃圾回收机制

在Python中,一旦某个对象不再被任何变量所引用,那么这个对象就称为“垃圾”。为了避免内存泄露和浪费,Python中有自动的垃圾回收机制,即Python会自动回收这些垃圾对象所占用的内存空间。

Python的垃圾回收机制主要基于两种方法:

  • 引用计数(Reference Counting):对于每个对象,Python会维护一个计数器来统计它被引用的次数。每当一个对象被一个新的变量引用时,这个计数器会加1;当一个变量指向其他对象、被删除、或者作用域结束时,这个计数器会减1。当一个对象的引用计数为0时,Python就会自动回收它所占用的内存空间。

  • 标记清除(Mark and Sweep):在Python运行过程中,Python会定期检查那些引用计数为0的对象并进行回收。同时,为了回收那些存在循环引用(即一组对象相互引用,但是没有其他任何变量引用它们)的对象,Python还会使用标记清除的方式:通过GC算法,确定所有被引用的对象,并标记它们。然后,将未标记的对象视为垃圾并进行回收。

例如:

a = [1, 2, 3]
b = a     # b引用了a引用的对象
a = None  # 将a变量的值设为空,不再引用对象

在这个例子中,当执行完a = None的语句后,只有变量b还引用着[1, 2, 3]这个列表对象。因此,Python的垃圾回收机制会自动回收原来存储[1, 2, 3]的内存空间。

示例说明

例子一:可变类型的副作用

a = [1, 2, 3]
b = a

# 修改a的值
a[0] = 4

print(a) # 输出 [4, 2, 3]
print(b) # 输出 [4, 2, 3]

在这个例子中,我们创建了一个列表对象[1, 2, 3],然后用变量a来指向它,再用变量b也指向它。这样,a和b都“引用”了同一个对象。因此,修改a的值,也会改变b的值,反之亦然。

例子二:循环引用的内存泄露

class Node:
    def __init__(self):
        self.next = None

# 创建两个对象
a = Node()
b = Node()

# 两个对象相互引用
a.next = b
b.next = a

# 删除变量a和b
del a
del b

在这个例子中,我们创建了两个Node对象,每个对象都有一个next属性,分别引用着对方。因此,这两个对象存在“循环引用”的问题。当删除a和b变量时,虽然它们不再引用这两个对象,但是由于这两个对象互相引用,它们的引用计数都不为0。因此,Python的垃圾回收机制并不能回收这两个对象,导致内存泄露。