对象引用,可变性和垃圾回收
1 变量到底是什么
1.1 变量到底是什么
在Python中,变量是对象的引用,而不是像C语言那样的内存位置。**变量只是指向对象的名称,并且可以更改指向的对象(动态语言)。**Python中的对象是动态分配的,因此变量在使用前不需要事先声明数据类型。Python中的垃圾回收器处理对象的内存管理,这使得Python编程更容易,因为您不必手动分配或释放内存。
也可以说其实变量是一个指针,例子如下。
x = 42
y = x
print(id(x),id(y)) # 4369100368 4369100368
x = 100 # 重新引用了一个对象
print(y) # 输出 42
print(id(x),id(y)) # 4369290704 4369100368
在这个例子中,变量 x
最初引用整数对象 42
。然后,变量 y
也引用相同的对象。当 x
被重新赋值为 100
时,它现在引用一个新的整数对象。但是,变量 y
仍然引用原始的整数对象 42
,因为它的值没有更改。
对于这种引用机制,python也有自己的优化方法
举个例子,如果我们在 Python 中定义了两个字符串变量 s1
和 s2
,它们的值相同:
s1 = "hello"
s2 = "hello"
这时候,Python 会自动将这两个变量引用同一个字符串对象,而不是为每个变量分配一个新的对象。这样做有助于减少内存占用,并提高程序的执行效率。
我们可以通过 id
函数来查看变量引用的对象的内存地址:
print(id(s1))
print(id(s2))
如果输出的两个地址相同,那么说明这两个变量引用的是同一个对象,即 Python 进行了对象重用的优化。
1.2 ==和is的区别
在Python中,==
用于比较两个对象的值是否相等,而is
用于比较两个对象是否是同一个对象,是否引用同一块内存地址。具体来说,==
比较的是两个对象的值是否相等,而is
比较的是两个对象的身份是否相等。举个例子:
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a == b) # True, 值相等
print(a is b) # True, 引用同一个对象
print(a == c) # True,值相等
print(a is c) # False,不是同一个对象
注意,在Python中,小整数和一些字符串等常量对象会被缓存并重复使用,所以is
运算符比较这些对象时通常会返回True
。例如:
x = 10
y = 10
print(x is y) # True
s1 = "hello"
s2 = "hello"
print(s1 is s2) # True
2 python中的垃圾回收机制
2.1 垃圾回收机制
Python中的垃圾回收机制使用自动化的内存管理来跟踪和回收不再使用的内存。Python中的垃圾回收器利用了引用计数来确定对象何时可以安全地删除。当对象的引用计数归零时,垃圾回收器会自动将其清除。一般情况下,了解引用计数即可。
Python中的垃圾回收器还使用分代回收来进一步提高内存管理的效率。这个概念基于一个简单的事实,即大多数对象都是短期的,只在它们被创建的那一时刻有用。因此,Python将对象分为三代:第一代包含新创建的对象,第二代包含在第一代中存活了一段时间的对象,第三代包含在第二代中存活了更长时间的对象。Python会更频繁地回收第一代对象,而较少回收第二代和第三代对象,从而提高了内存管理的效率。
此外,Python中的垃圾回收器还使用了循环垃圾收集。这个概念是指当两个或多个对象引用彼此时,它们之间形成了循环引用。这种情况下,垃圾回收器无法确定哪个对象应该被删除,因为它们都在彼此引用。为了解决这个问题,Python中的垃圾回收器使用了一种称为标记清除的算法,该算法可以检测和清除循环引用的对象。
2.2 del和垃圾回收的区别
Python中的del
语句可以用于删除变量和列表中的元素。当您删除一个变量时,**它会从命名空间中删除该变量。**如果变量引用的对象的引用计数归零,则垃圾回收器将自动清除该对象。
下面是一些示例代码,演示了如何使用del
语句删除变量和列表元素:
# 删除变量
x = 42
del x
# 删除列表元素
a = [1, 2, 3]
del a[0]
在这个例子中,我们首先创建一个整数变量x
,然后使用del
语句删除它。接下来,我们创建一个包含三个元素的列表a
,然后使用del
语句删除第一个元素。
当您删除一个列表元素时,垃圾回收器不会立即删除它。相反,它会将该元素的引用计数减1。只有当该元素的引用计数归零时,垃圾回收器才会将其清除。这意味着如果您删除一个列表中的元素,但是其他变量仍然引用该元素,则该元素不会立即被清除。
最后,值得注意的是,del
语句只能删除变量和列表中的元素。它不能删除整个对象。如果您想删除整个对象,必须确保没有任何变量引用该对象,并且该对象的引用计数为零。在这种情况下,垃圾回收器将自动清除该对象。
魔法函数__del__(self)
就是析构函数,来回收对象时执行操作
2.3 python中的一个经典的参数错误
在Python中,经典的参数错误之一是将可变对象作为默认参数传递给函数。例如,考虑以下函数:
def append_to(element, to=[]):
to.append(element)
return to
这个函数将一个元素添加到一个列表中,并返回该列表。它还有一个可选参数to
,默认为一个空列表。如果没有指定这个参数,则使用默认的空列表。
然而,这个函数有一个问题。由于默认参数只会在函数定义时被计算一次,因此如果多次调用这个函数而没有指定to
参数,则所有调用都将共享同一个默认列表。这意味着在一个调用中添加的元素可能会影响到下一个调用。
例如,考虑以下代码:
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2]
print(append_to(3)) # [1, 2, 3]
在这个例子中,我们首先调用append_to
函数,并将整数1
作为参数传递。由于没有指定to
参数,使用默认的空列表。因此,该函数返回一个包含整数1
的列表。
接下来,我们再次调用append_to
函数,并将整数2
作为参数传递。由于没有指定to
参数,该函数将使用相同的默认列表作为第一个调用。因此,该函数返回包含整数1
和2
的列表。
最后,我们第三次调用append_to
函数,并将整数3
作为参数传递。由于没有指定to
参数,该函数将再次使用相同的默认列表。因此,该函数返回包含整数1
、2
和3
的列表。
由于所有调用共享同一个默认列表,因此在第二次和第三次调用中添加的元素也出现在第一次调用的结果中。
要避免这个问题,可以将默认值设置为None
,然后在函数内部检查是否传递了参数。如果没有传递参数,则可以创建一个新列表。例如,以下是一个修复后的append_to
函数:
def append_to(element, to=None):
if to is None:
to = []
to.append(element)
return to
这个函数首先检查是否传递了to
参数。如果没有传递,则将其设置为一个新的空列表。然后,它将元素添加到该列表中,并返回该列表。由于每次调用都会创建一个新列表,因此不会出现元素重叠的问题。