本章内容:
对象引用 :
引用式变量
对象标识符,值,别名
可变性
元组特性
潜复制和深复制
引用和函数参数
垃圾回收
删除对象
弱引用
提示:本章内容理解不清很可能出现不易发现的错误
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
?