python对象引用,可变性和垃圾回收
对象引用和可变性
变量不是盒子,而是‘便利贴’
>>> a = [1,2,3] >>> b = a >>> a.append(5) >>> a [1, 2, 3, 5] >>> b [1, 2, 3, 5]
变量的赋值方式:比如x = 2是将一个变量s分配给一个对象比如整数2。而不是把整数对象2分配给变量s
>>> c = {'name':'yang','born':1997} >>> a = c #a为c的一个别名。他们俩同时指向一个对象,'=='和'is'运算符证明这一点 >>> a == c True >>> a is c True >>> id(a),id(c) (139644203394464, 139644203394464) >>> a['name'] = 'yyy' #用a修改内容 >>> c #c也会被修改,因为它们俩指向的是一个对象 {'name': 'yyy', 'born': 1997} >>> d = {'name': 'yyy', 'born': 1997} #新建一个d对象,与a和c的值一样 >>> d == a # '=='运算符比较值是否相等 True >>> d is a #'is'运算符比较对象的标识是否相等,就是比较id()是否相等。d是新建的对象很明显不会相等 False >>> id(d), id(a) (139644203394536, 139644203394464)
每个变量都有标识、类型和值。对象一旦创建,他的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id()函数返回对象标识的整数表示。
元组的不可变性
#元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。比如元组里引用了一个可变对象列表,不能改变这个引用让他变成其他字典或整数对象,但是可以修改这个可变对象的值。 >>> t1 = (1, 2, [3, 4]) >>> t2 = (1, 2, [3, 4]) >>> id(t1),id(t2) (139644201933272, 139644201953896) >>> t1 == t2 #值相等 True >>> t1 is t2 #标识不相等,两个除了值相等其他完全不相关的变量 False >>> t1[-1].append(5) #可以对元组内的列表元组进行添加操作 >>> t1 == t2 #此时他们俩的值不相等了 False
不显式的使用copy模块的deepcopy函数深复制时,都默认为浅复制
浅复制复制了最外层的容器,副本中的元素是原容器中元素的引用
>>> a = [1, 2, [3, 4]] >>> c = a[:] >>>a == c True >>> a is c #容器不一样,但是里元素的引用一样 False >>> r = (1, 2, [4,5]) #对元组或其他不可变类型对象浅复制返回的是同一个对象的引用。类似于rr = r >>> rr = r[:] >>> rr is r True #浅复制后母本和副本内的元素都互为对方的标识,也就是都指向同一个对象。如果对母本或副本中的可变元素操作,因为两个引用是同一个对象,所以会影响到另一个母本或副本。但是,比如在副本中对不可变元素操作会生成一个新的对象引用,就和母本中的不可变元素不是同一个引用了,就不会影响到母本。 #下面是示例: >>> l1 = [3, [66,55,44], (7, 8, 9)] >>> l2 = list(l1) #对列表l1浅复制,赋值给l1 >>> l1.append(100) #l1添加一个新元素100 >>> l2 [3, [66, 55, 44], (7, 8, 9)] #l2中没有添加 >>> l1 [3, [66, 55, 44], (7, 8, 9), 100] #l1中添加成功 >>> l1[1].remove(55) #将l1[1]这个列表中的55元素删除 >>> l1 [3, [66, 44], (7, 8, 9), 100] >>> l2 [3, [66, 44], (7, 8, 9)] #对l2也有影响,因为l2[1]这个列表和l1[1]的列表时同一个,他们两个互相时对方的别名,都指向同一个列表元素 >>> l2[1] += [1,1,1] #l2[1]就地修改列表 >>> l2 [3, [66, 44, 1, 1, 1], (7, 8, 9)] >>> l1 [3, [66, 44, 1, 1, 1], (7, 8, 9), 100] #l1[1]也被修改,因为这是同一个对象 >>> l1[2] is l2[2] #此时l1[2]和l2[2]这两个元组是同一个对象 True >>> l2[2] += (1,1,1) #对l2[2]这个元组添加(1,1,1)。因为元组是不可变元素,这个赋值操作不能就地添加,相当于l2[2] = l2[2]+(1,1,1),这里创建了一个新元组。 >>> l1[2] is l2[2] #此时l1[2]和l2[2]这两个元素不再是同一个对象 False >>> l2 [3, [66, 44, 1, 1, 1], (7, 8, 9, 1, 1, 1)] >>> l1 [3, [66, 44, 1, 1, 1], (7, 8, 9), 100] #所以这个修改并没有对l1起作用 >>> l2[1] is l1[1] #可变对象就地修改,再改还是引用的同一个对象 True >>> l1[0] is l2[0] True
深复制
#定义一个类来测试 class Bus: def __init__(self, p=None): if p is None: p = [] else: self.p = list(p) def pick(self, name): self.p.append(name) def drop(self, name): self.p.remove(name) >>> from bus import * >>> bus1 = Bus(['a', 'b', 'cc']) >>> bus1 <bus.Bus object at 0x7fb47e6ccba8> >>> bus1.p ['a', 'b', 'cc'] >>> bus2 = copy.copy(bus1) #浅复制bus1 >>> bus3 = copy.deepcopy(bus1) #深复制bus1 #到此 创建了三个bus实例 >>> bus1.drop('a') #bus1的p列表中删除一个元素 >>> bus2 <bus.Bus object at 0x7fb47e6cc7b8> >>> bus2.p #bus2的p列表中也没有这个元素了,浅复制共享一个列表对象 ['b', 'cc'] >>> bus3.p #深复制不会共享列表,所以不会修改 ['a', 'b', 'cc'] >>> bus1.p is bus2.p True >>> bus1.p is bus3.p False
函数的可变参数
#函数可能会修改接收到的任何可变对象 >>> def f(a, b): ... a += b ... print(id(a)) ... return a >>> x = [1,1] >>> id(x) 139901112369928 >>> y = [2, 2] >>> f(x, y) #函数的形参获得各个实参的副本,也就是说,函数内部的形参是实参的别名 139901112369928 [1, 1, 2, 2] >>> x [1, 1, 2, 2] >>> x = 1 #当实参为不可变类型时 >>> y = 2 >>> id(x) 10910400 >>> f(x, y) # a += b相当于重新创建了一个a对象,上面的浅复制讲的很清楚了 10910464 3
函数的默认值是可变参数时
class Bus: def __init__(self, p=[]): self.p = p def pick(self, name): self.p.append(name) def drop(self, name): self.p.remove(name) >>> from bus import * >>> bus1 = Bus(['a', 'b']) >>> bus2 = Bus() >>> bus3 = Bus() #创建三个实例,bus3和bus3使用默认值 >>> bus1.pick('c') #bus1添加新元素 >>> bus1.p,bus2.p,bus3.p #只有bus1变了 (['a', 'b', 'c'], [], []) >>> bus2.pick('e') #bus2添加新元素 >>> bus1.p,bus2.p,bus3.p #bus2和bus3都变了 (['a', 'b', 'c'], ['e'], ['e']) >>> Bus.__init__.__defaults__ #这时Bus类的defaults属性已经变了 (['e'],)
上面很明显的说明了:bus2和bus3使用了参数默认值(列表对象)。默认值一是在模块加载时计算,self.p变成了p参数默认值的别名。就是说不管多少个实例,只要使用的是默认值(列表对象),那么所有实例和Bus类共享这一个列表。
函数的默认值是可变参数时的解决办法
#错误的方法 class Bus: def __init__(self, p=None): if p is None: self.p = [] else: self.p = p #self.p为p的别名,他们俩都指向同一个对象。 def pick(self, name): self.p.append(name) def drop(self, name): self.p.remove(name) >>> from bus import * >>> i = ['a', 'b', 'c'] >>> b = Bus(i) >>> b.drop('a') # b实例调用他的drop方法删除'a'的时候把i列表中的'a'也删了 >>> b.p ['b', 'c'] >>> i ['b', 'c'] #正确方法 class Bus: def __init__(self, p=None): if p is None: self.p = [] else: self.p = list(p) #这时self.p是对p的一个浅复制,self.p和p指向不同对象,但是容器里面的元素还是相同的引用,如果元素为可变类型,那么还是会出现问题 def pick(self, name): self.p.append(name) def drop(self, name): self.p.remove(name) >>> from bus import * >>> i = ['a', 'b', 'c'] >>> b = Bus(i) >>> b.drop('a') # b实例删除'a'后i列表并没有受到影响 >>> i ['a', 'b', 'c'] >>> i = ['a', 'b', [1,2]] #如果参数内元素是可变类型还是有影响 >>> b = Bus(i) >>> b.p[2].pop() # b实例删除列表内的一个列表中的元素 2 >>> i # i列表也受到影响 ['a', 'b', [1]]
总结:浅复制复制的是最外层的容器,里面的元素还是原容器中元素的引用,也就是修改里面的可变元素两个容器都会受到影响。深复制相当于重新创建了一个对象,里面的元素和原容器一点关系都没有。
垃圾回收
在cpython中,垃圾回收的主要算法是引用计数。每个对象都会统计有多少引用指向自己。当计数归零时对象就立即被销毁。当然python还有其他更复杂的垃圾回收算法,而且不依赖引用计数。
>>> import weakref >>> s1 = {1,2,3} >>> s2 = s1 #s2 is s1。指向同一个集合 >>> def a(): ... print('aaa') >>> end = weakref.finalize(s1, a) # weakref是一个弱引用包。这里在s1引用对象上注册a回调 >>> end <finalize object at 0x7faa0a5fa3b0; for 'set' at 0x7faa0a3e6048> >>> del s1 >>> end.alive #对象还没有被销毁 True >>> s2 = 2 # 让s2指向其他对象,此时没有对那个集合的引用。对象被销毁执行回调函数输出'aaa' aaa