对象引用、可变性和垃圾回收
对象的标注、引用、别名
Python变量类似于Java中的引用式变量,因此最好把他们理解为附加在对象上的标注。
Python赋值语句应该始终先读右边。
对象在右边创建或获取,再此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。
变量只不过是标注,所以无法阻止为对象贴上多个标注。贴的多个标注,就是别名。
>>> f1 = {'name':'Li','age':19} >>> f2 = f1 # f2是f1的别名 >>> f2 is f1 True
每个变量(object)都有标识(identity)、类型(type)和值(value)。
对象一旦创建,它的标识绝不会变。可以把标识理解为对象在内存中的地址。
is运算符比较两个对象的标识,id()函数返回对象标识的整数表示,==运算符比较两个对象的值(对象中保存的数据)。
▲ 对+= 或 *= 所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象,如果是可变对象,会就地修改。
元组的相对不可变性
元组的相对不可变性:
元组与多数Python集合(列表、字典、集)一样,保存的是对象的引用。
如果引用的元素是可变的,即便元组本身不可变,元素依然可变。
copy、deepcopy
浅复制:(复制最外层容器,副本中的元素是源容器中元素的引用)
复制列表最简单的方式:
(1)使用构造方法
(2)使用 [ :]
>>> l1 = [1,2,3,4] >>> l2 = list(l1) >>> l3 = l1[:]
深复制:副本不共享内部对象的引用
class Bus: def __init__(self,passengers=None): if passengers is None: self.passengers = [] else: self.passengers = list(passengers) def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) import copy bus1 = Bus([1,2,3,'David','Tom']) bus2 = copy.copy(bus1) bus3 = copy.deepcopy(bus1) print(id(bus1),id(bus2),id(bus3)) print(id(bus1.passengers),id(bus2.passengers),id(bus3.passengers)) bus1.drop('David') bus1.pick(998) print(bus1.passengers) print(bus2.passengers) print(bus3.passengers)
函数参数传递模式
函数参数作为引用时:
Python唯一支持的参数传递模式:共享传参(call by sharing)
共享传参:指函数各个形式参数获得实参中各个引用的副本。(函数内部的形参是实参的副本)
def f(a,b): a += b return a a = 1 b = 2 c = f(a,b) >>> print(c) 3 >>> print(a,b) 1,2 # ======== def f(a,b): a += b return a a = [1,2] b = [3,4] c = f(a,b) >>> print(c) [1, 2, 3, 4] >>> print(a,b) [1, 2, 3, 4] [3, 4]
避免使用可变类型作为参数的默认值
bus_team = ['Sue','David','Pat','Maria'] bus = Bus(bus_team) bus.drop('David') bus.drop('Pat') >>> bus_team ['Sue','Maria']
在类中直接把参数赋值给实例变量等于为参数对象创建别名。
class Bus: def __init__(self,bus_team): if bus_team is None: self.bus_passengers = [] else: self.bus_passengers = bus_team # ============= class Bus: def __init__(self,bus_team): if bus_team is None: self.bus_passengers = [] else: self.bus_passengers = list(bus_team)
垃圾回收机制
del和垃圾回收:
del语句删除名称,而不是对象。
del命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。
在Cpython中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。
当引用计数归零时,对象立即就被销毁。Cpython会在对象上调用__del__方法,然后释放分配给对象的内存。
>>> import weakref >>> s1 = {1,2,3} >>> s2 = s1 >>> ender = weakref.finalize(s1,bye) # 注册一个回调函数 >>> ender.alive True >>> del s1 >>> ender.alive True >>> s2 = '123' Gone with the wind >>> ender.alive False
弱引用:
正因为有引用,对象才会在内存中存在。
弱引用不会增加对象的引用数量。
引用的目标对象称为所指对象(referent),弱引用不会妨碍所指对象被当作垃圾回收。
WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。
被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary中删除。
class Cheese: def __init__(self,kind): self.kind = kind >>> import weakref >>> stock = weakref.WeakValueDictionary() >>> catalog = [Cheese('Red Leicester'),Cheese('Tilsit'),Cheese('Parmesan')] >>> for cheese in catalog: stock[chess.kind] = cheese >>> sorted(stock.keys()) ['Parmesan','Red Leicester','Tilsit'] >>> del catalog >>> sorted(stock.keys()) ['Parmesan'] >>> del cheese >>> sorted(stock.keys()) []
▲ for循环中的变量cheese是全局变量,除非显式删除,否则不会消失。
weakref 模块还提供了WeakSet 类,如果一个类需要知道所有实例,一种好的方案是创建一个WeakSet 保存实例引用。
常规Set实例永远也不会被垃圾回收,因为类中有实例的强引用。而类存在的时间与Python进程一样长,除非显式删除类。
弱引用的局限:
基本的list和dict实例不能作为所指对象,但是他们的子类可以。set实例和用户自定义类型可以作为所指对象。
同时int和tuple实例不能作为弱引用的目标,甚至它们的子类也不行。
class Mylist(list): ... a_list = Mylist(range(10)) wref_to_a_list = weakref.ref(a_list)