本章内容:
对象引用 :
引用式变量 对象标识符,值,别名
可变性
元组特性 潜复制和深复制 引用和函数参数
垃圾回收
删除对象 弱引用
提示:本章内容理解不清很可能出现不易发现的错误
8.1 变量不是盒子
#a是对象[1,2,3]的标识,或者说引用式变量 a=[1,2,3] #b也是对象[1,2,3]的标签,或者说别名 b=a a.append(4) print(b)
对引用式变量来说,说把变量分配给对象更合理
8.2 标识、相等性和别名
charles={"name":"Charles L","born":1832} #lewis是charles的笔名 lewis=charles lewis is charles id(charles),id(lewis) #此时有一个冒充者: alex={"name":"Charles L","born":1832} #==比较的是值 alex==charles #is比较的是对像的id值 alex is not charles
==运算符比较两个对象的值(对象中保存的数据),而is比较对象的内存地址。
8.2.1 在==和is之间选择
通常我们使用==更多,因为我们通常关心的是对象的值
最常使用is检查变量绑定的值是不是None。下面是推荐的写法:
x is None
此是is运算符的调用速度更快,因为is运算符不能重载,所以Python不用寻找并调用特殊方法
8.2.2 元组的相对不可变性
元组保存的是对象的引用,元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关
即元组不可变的仅仅是元素的标识,而指向的对象是可变的
t1=(1,2,[30,40]) t2=(1,2,[30,40]) t1==t2 id(t1[-1]) t1[-1].append(99) id(t1) t1==t2
所以元组只具有相对不可变性,这是元组不可散列的原因
8.3 默认做浅复制
浅复制
l1=[3,[55,44],[7,8,9]] #[:]构造函数默认做浅复制 l2=l1[:] l2 l1 l2==l1 l2 is l1 l1[1] is l2[1] l3=la.copy() #该方法也是潜复制 l4=list(l1) l5=list(l1) #也是潜复制
深复制(即副本不共享内部对象的引用)
import copy l5=copy.deepcopy(l1)
用下例看潜复制和深复制的差异
class Bus: def __init__(self,passengers=None): #创建一个passenger列表 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(["Alice","bill","Charles","David"]) bus2=copy.copy(bus1) #用copy模块直接对变量进行复制,这里是潜复制 bus3=copy.deepcopy(bus1) #用copy模块中的deepcopy方法进行深复制 bus1.drop("Alice") print(bus2.passengers) print(bus3.passengers) print(id(bus1.passengers)==id(bus2.passengers)) #bus2是bus1的浅复制,和bus共享同一个乘客列表 print(id(bus1.passengers)==id(bus3.passengers))
8.4 函数的参数作为引用时
Python唯一支持的参数传递模式是共享传参
共享传参
:函数内部的形参是实参的别名
这种传参方式的结果:函数可能会修改作为参数传入的可变对象
当传入参数是不可变对象,比如整数、元组时,改变形参并不能把 实参改变。
def f(a,b): a+=b return a x=1 y=2 f(x,y) print(x,y) #元组 t=(10,20) u=(30,40) f(t,u) print(t,u)
特别注意当参数是可变对象时,例如列表
x=[1,2] y=[3,4] f(x,y) print(x,y)
8.4.1 不要使用可变类型作为参数的默认值
python函数中,可选参数可以有默认值。
这样可以实现向后兼容。
(向后兼容:例如在python2.0上写的程序,在python3.0上可以完满运行时,我们称这种现象叫向后兼容,或者叫向历史兼容)
使用可变对象作为参数的默认值,有可能会造成不容易发现的错误。
看下例:
class funnyBus: """人会失踪的神奇校车""" def __init__(self,passengers=[]): #这个将一个列表(可变对象)设置为默认参数 self.passengers=passengers def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) bus1=funnyBus(["uni","xiaoma","leo"]) #此时传入了一个列表参数。这时候,没有什么问题 bus1.passengers bus1.pick("alice") bus2=funnyBus() bus3=funnyBus()
这个时候问题就来了,bus2和bus2两个实例会共享一个默认的列表[]
我们查看一下这时类的属性。
bus2=funnyBus() bus2.pick("hello") bus3=funnyBus() bus2.pick("hello ni mei") print(dir(funnyBus.__init__)) print(funnyBus.__init__.__defaults__)
output:
(['hello', 'hello ni mei'],)
运行下代码:
print(funnyBus.__init__.__defaults__[0] is bus2.passengers) #实例bus2的属性passengers帮定到了类的第一个属性上。
正确的处理默认参数的方法:
class Bus: def __init__(self,passengers=None): #创建一个passenger列表 if passengers is None: #如果没有默认参数,实例就自己创建个参数。 self.passengers=[] else: self.passengers=list(passengers)
8.4.2 防御可变参数
如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。
下面例子说明可变参数的风险:
class Bus: def __init__(self,passengers=None): #创建一个passenger列表 if passengers is None: self.passengers=[] else: self.passengers=passengers #(1) def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name)
此时,将可变参数传入了对象,当对象改变该参数时,外面的可变参数也会改变。因为是参数的引用传入了。
所以,如果我们不希望改变传入的参数,
上述代码的(1)部分应该这么写
self.passengers=list(passengers) #创建参数的副本
这么写还有一个好处,就是还可以传入元组,等其他参数。
在不确定是否需要改变参数对象的时候,我们会先默认采用这种方法。
8.5 del和垃圾回收
对象绝不会自行销毁;然而,无法得到对象时,可能会被当作垃圾回收。
del语句删除引用,而不是对象。当del删除的引用,是该对象的唯一引用时,该对象会被垃圾回收。
python垃圾回收机制:
引用计数法
当引用计数归零时,对象立即就被销毁。
下面例子演示了对象的回收。
import weakref s1={1,2,3} s2=s1 def bye(): print("Gone....") ender=weakref.finalize(s1,bye) ender.alive def sl ender.alive s2="spam" ender.alive
8.6 弱引用
实际使用中,有时需要引用对象,但是不让对象存在的时间超过所需时间。
例如我们把数据对象保存在缓存中使用,当对象的引用不存在,我们希望将缓存中的这部分数据释放掉。这时候我们就可以使用弱引用。
对弱引用的原理解释:
弱引用底层实现原理
弱应用的一个使用场景。
假设我们有一个多线程程序,并发处理应用数据:
class Date: def __init__(self,key): pass
key是该数据的唯一标识,该数据可能同时被多个线程同时访问。
当Data数据量很大时,创建的成本很高,我们希望Data在程序中只维护一个副本,
就算被多个线程同时访问,也不重复创建。
可以设计一个缓存中间件Cacher
import threading #数据缓存 class Cacher: def __init__(self): self.pool={} self.lock=threading.Lock() def get(self,key): with self.lock: #加锁 data=self.pool.get(key) if data: return data self.pool[key]=data=Data(key) return data
该代码有个严重的问题,
Data一旦穿件,就会保存在数据字典,哪怕已经没有线程需要该数据,因为该数据在字典中有键进行了引用,所以对象不会被回收。
这个时候就用到弱引用,用字典键值对中的值来保存数据对象的弱引用,当数据对像的引用为0之后,就会自动释放该对象。
import threading import weakref # 数据缓存 class Cacher: def __init__(self): self.pool=weakref.WeakValueDictionary() self.lock = threading.Lock() def get(self, key): with self.lock: data = self.pool.get(key) if data: return data self.pool[key] = data = Data(key) return data
弱引用类在 weaker模块中,
常见的有:
weakValueDictinary
weakKEYDictinary
weakSet
最后看一个小程序,猜一个结果:
t1=(1,2,3)
t2=tuple(t1)
t2 is t1
?
t3=t1[:]
t3 is t1
?
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)