对象引用、可变性和垃圾回收

变量

  • python变量不是盒子,是标注。
  • 创建在前,分配在后。是把变量分配给对象,而不是把对象分配给变量。
  • 一个对象可以分配多个标签,而一个标签只能贴到一个对象上。

is==

  • ==运算符比较两个对象的值,调用对象的__eq__方法。如果是自定义类型,并且没有重载__eq__方法,则==会比较两个对象的id–标识。
  • is比较对象的id
  • 在变量和单例值之间比较时应该使用is,特别是检查变量绑定的值是不是None时。因为is方法不能被重载,速度更快。不用像==那样去寻找有没有重载__eq__方法。

元组的相对不可变性

tuple虽然被划分为不可变序列,但是相对于str而言,tuple的不可变性又有自己的特点。对于不可变序列在tuple直接保存,而对于可变序列则保存引用。

1
2
3
4
5
6
7
8
9
10
11
import sys			//改变元组内可变序列的值,而元组的内存占用并没有改变。

t1 = (1, 2, [30, 40])
t2 = (1, 2, 3, 4, 5, [30, 40])

print("the memory of ", t1, " = ", sys.getsizeof(t1))
print("the memory of ", t2, " = ", sys.getsizeof(t2))

t1[-1].append([1,2,3,4,5,6,7,8])

print("the memory of ", t1, " = ", sys.getsizeof(t1))

由此我们可以得出,其实元组真的是不可变的。元组内只是保存了可变序列的引用,而我们修改的也是可变序列的值,并没有修改它的标识。所以某种程度上元组的值并没有改变……….

深拷贝与浅拷贝

这是一个好网站,网站的动画演示非常到位,可以把下面的代码输进去试一下。

1
2
3
4
5
6
7
8
9
10
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
print('l1: ', l1)
print('l2: ', l2)
l2[1] += [33, 22]
l2[2] += (10, 11)
print('l1: ', l1)
print('l2: ', l2)

__deepcopy__

deepcopy()会调用__deepcopy__方法来进行深拷贝。深拷贝时对于可变与不可变序列都会创建新的对象,而不是增加引用。deepcopy函数会记住已经复制的对象,因此能优雅的处理循环引用。

函数的参数

函数的参数不能是可变序列

函数的参数不能是可变序列
函数的参数不能是可变序列
函数的参数不能是可变序列
重要的事情说三遍!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class HauntedBus:
def __init__(self, passagers=[]):
self.passagers = passagers

def pick(self, name):
self.passagers.append(name)

def drop(self, name):
self.passagers.remove(name)

bus1 = HauntedBus()
bus1.pick('darren')
bus2 = HauntedBus()

print(bus1.passagers)
print(bus2.passagers)

HauntedBus中,passagers的默认值是一个空列表,也可以说它引用了一个空列表。当我们对这个空列表做增删操作时,使得其中的值发生改变。但是注意的是之后所有的HauntedBus实例中,passagers都是引用了同一个列表,因此之后创建的所有实例,其passagers属性都不为空。

防御可变参数

当传入的参数是可变序列是,并且我们要修改它的内容,但是不希望对原值产生影响。这时我们便需要注意一些事项,可以选择用工厂函数产生这个参数的副本,对其副本进行操作,便可以避免对原参数产生影响。这里可以类比C语言中的值传递和指针传递(也可以是名字传递)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class HauntedBus:
def __init__(self, passagers=None):
if passagers is None
self.passagers = []
else
self.passagers = list(passagers)
# self.passagers = passagers # “错误”操作

def pick(self, name):
self.passagers.append(name)

def drop(self, name):
self.passagers.remove(name)

bus1 = HauntedBus()
bus1.pick('darren')
bus2 = HauntedBus()

print(bus1.passagers)
print(bus2.passagers)

垃圾回收

对象绝不会自行销毁;然而,无法得到对象时,可能会被当作垃圾回收。

del

  • del命令可能会导致对象被当作垃圾回收,但是更多的时候仅仅是删除对象的一个引用。
  • Cpython2.0增加的分代垃圾回收算法,可以检测引用循环中涉及的对象组,如果一组对象之间相互引用,导致无法获取,则被回收。
  • 一般情况下我们不需要显示地调用del函数,python会帮我们做好内存回收工作。

弱引用

引用对象,但不增加对象的引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import weakref
a_set = {0, 2}
wref = weakref.ref(a_set) # 增加一个弱引用,但是不会增加对象的引用计数

wref()
a_set = {3, 5, 6}

wref() # 之所以可以继续得到{0, 2}是因为在控制台中,{0, 2}与_变量绑定,增加了引用计数
wref() is None # 执行过后True与_绑定,{0, 2}的引用计数为零,被回收。

wref()
_

wref() is None

本文标题:对象引用、可变性和垃圾回收

文章作者:Darren

发布时间:2018年09月20日 - 00:09

最后更新:2018年09月20日 - 00:09

原始链接:http://Darren2017.github.io/2018/09/20/对象引用、可变性和垃圾回收/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。