Python的垃圾回收机制,
055-79000读书笔记——Python对象引用、可变性和垃圾收集_ 12570095的技术博客_博客
该变量是一个引用a=[1,2,3]
b=a
a .追加(4)
b
[1,2,3,4]a和B指的是同一个列表。
每个变量都有一个身份、类型和值。一旦一个对象被创建,它的身份将永远不会
改变;你可以把标识符理解为对象在内存中的地址。Is运算符比较两个
对象的标识;id()函数返回对象ID的整数表示。
在==和is之间选择==运算符比较两个对象的值(存储在对象中的数据),而is比较
识别(即确定是否是同一物体)。
有两种方法可以检查x是否为None:
在[10]中:如果不是x:
.打印( x是无)
.
x是零
在[11]中:如果x是无:
.打印( x是无)
.
x是Noneis运算符比==更快,因为它不能重载,所以Python不需要查找和调整。
用一个特殊的方法,但是直接比较两个整数id。而a==b是语法糖,相当于
在a.__eq__(b)处。
从object继承的__eq__方法比较两个对象的id,结果是一样的。
在[15]中:B类(对象):
.def __init__(self,value):
.自我。_value=值
.
在[16]中:a=B(1)
在[17]中:b=B(1)
在[18]中:a==b
Out[18]:假
在[19]中:a是b
Out[19]:假
在[20]中:id(a)
Out[20]: 140510725545488
在[21]中:id(b)
Out[21]: 140510722278792
在[22]中:a是B
Out[22]:假
在[23]中:id(B)
Out[23]: 21494392
在[24]中:式(a)==B
Out[24]: True,但是考虑到object属性的值,大多数内置类型以更有意义的方式重写__eq__方法。
元组中相对不可变的元组保存对象的引用。如果被引用的元素是可变的,那么即使元组本身是不可变的,元素也是可变的。
在[25]: t1=(1,2,[30,40])
在[26]中:t2=(1,2,[30,40])
在[27]中:t1==t2
Out[27]:真的
In [28]: id(t1[-1]) #t1[-1]是最后一个列表元素。
Out[28]: 140510930522568
在[29]中:t1[-1]。追加(99)
在[30]: t1
Out[30]: (1,2,[30,40,99])
在[31]: id(t1[-1])
Out[31]: 140510930522568
在[32]中:t1==这也是有些元组不哈希的原因。
做默认的浅层复制拷贝列表(或者大多数内置可变集合)的最简单的方法是使用内置的类型构造方法。例如:
在[33]中:l1=[3,[55,44],(7,8,9)]
在[34]: l2=list(l1) #您也可以使用l2=l1[:]创建l1的副本
在[35]: l2
Out[35]: [3,[55,44],(7,8,9)]
在[36]中:l2==l1
Out[36]:真
在[37]中:l2是l1
Out[37]: False但是,构造函数or [:]做的是浅层复制(即复制的是元素引用)。如果所有元素都是不可变的,那么就没有问题,也可以节省内存。但是,如果存在可变元素,可能会导致意想不到的问题。
l1=[3,[66,55,44],(7,8,9)]
L2=列表(l1) # l2是l1的浅层副本。
l1 .追加(100) #
l1[1]。移除(55) #
打印( l1:,l1)
打印( l2:,l2)
l2[1]=[33,22] #
l2[2]=(10,11) #
打印( l1:,l1)
Print(l2:,l2)将代码复制到http://www.pythontutor.com/以可视化内存结构。
当执行第二行代码时,如上所示。L1和l2指向不同的列表,但都指向同一个列表[66,55,44]和元组(7,8,9)以及int实例。
l1=[3,[66,55,44],(7,8,9)]
l2=列表(l1) #
print(l1[0]是l2[0]) #True
print(l1[1]是l2[1]) #True
Print(l1[2]是l2[2]) #True它们三个元素一开始指的都是同一个对象,但为什么上图没有体现出来?请注意这里。
l1加100对l2没有影响。
看到这个我就明白了。像int这种不可变类型,是文字量,可以表示一个值,直接把值放入链表,不画内存结构。
删除内部列表l1[1]中的55。这对l2是有影响的,因为l2[1]绑定的列表和l1[1]是一样的。
对于可变对象,比如l2[1]引用的列表,运算符=就地修改列表。这个修改也反映在l1[1]中,因为它是l2[1]的别名。
对于元组,=运算符创建一个新的元组,然后将其重新绑定到变量。
l2[2]这相当于l2[2]=l2[2] (10,11)。现在,l1和l2中最后一个位置的元组不是同一个对象。
对任何对象进行深度复制和浅层复制都没问题,但有时我们需要的是深度复制(也就是说,副本不共享内部对
喜欢参考)。复制模块提供的Deepcopy和copy函数可以对任何对象进行深度复制和浅层复制。
为了演示copy()和deepcopy()的用法,一个简单的
班级巴士。这个类代表一辆载有乘客的校车,乘客会在途中上下车。
# -*-编码:utf-8 -*
类别总线:
def __init__(self,passengers=None):
如果没有乘客:
self.passengers=[]
否则:
self.passengers=list(乘客)
#老司机,带我一起走
定义选择(自己,姓名):
self.passengers.append(姓名)
#这不是去幼儿园的车,我要下车。
定义删除(自己,姓名):
Self.passengers.remove(name)在交互控制台中,执行以下代码:
导入副本
从总线导入总线
1=公交车([小明,小红,黄晓])
Bus2=copy.copy(bus1) #浅层复制
Bus3=copy.deepcopy(bus1) #深层复制
Id(总线1)、id(总线2)、id(总线3) #三个不同的总线实例
(2651381816680, 2651381846648, 2651382400280)
1.drop(小明)# 1路车的#小明下车。
2.bus2 .乘客#bus2也没有他。
[小红,黄晓]
ID (bus1.passengers),ID (bus2.passengers),ID (bus3.passengers) #可以看出,bus1和bus2共享同一个list对象。
(2651382392584, 2651382392584, 2651382272072)
3.bus3.passengers #和bus的乘客指向另一个列表
【小明,小红,黄晓】一般来说,深度临摹不是一件简单的事情。如果对象有循环引用,那么
这个简单的算法会进入一个无限循环。deepcopy函数会记住复制的线对。
大象,所以它可以优雅地处理循环引用。
a=[10,20]
b=[a,30]
追加(b)
a
[10, 20, [[.], 30]]
从副本导入深层副本
c=深层拷贝(a)
c
[10, 20, [[.],30]]深抄有时候会太深。例如,一个对象可能指的是不应被复制的外部。
或资源单例值。我们可以实现特殊的方法__copy__()和
__deepcopy__(),它控制copy和deepcopy的行为。
当函数的参数作为引用时,Python支持的唯一参数传递方式就是通过共享调用。Java简介
类型就是这样,基本类型是通过值传递的。
共享参数是指函数的形式参数获得实际参数中每个引用的副本。也就是说,
比方说,函数内部的参数是实际参数的别名。
这种模式的结果是,函数可以修改作为参数传入的可变对象,但是没有
修改这些对象的标识(即一个对象不能被另一个对象替换)。
定义f(a,b):
.a=b
.返回a
.
x=1
y=2
f(x,y)
三
X,y #数字X没有变,那些对象的识别方法不能修改。身体是(x=x y)
(1, 2)
a=[1,2]
b=[3,4]
女(阿,乙)
[1, 2, 3, 4]
a,b #列表a已更改。
([1, 2, 3, 4], [3, 4])
t=(10,20)
u=(30,40)
f(t,u)
(10, 20, 30, 40)
T,u #元组T没有改变
((10,20),(30,40))不要使用可变类型作为参数的默认值。我们基于上面的总线类定义了一个新类HauntedBus,然后修改了__init__方法。这一次,乘客的缺省值不是None,而是[],不必像以前那样使用if判断。这一“明智之举”会让我们陷入困境。
类HauntedBus:
#主要问题是传入的参数列表和HauntedBus中的列表会互相影响。
def __init__(self,passengers=[]):
乘客=乘客
#老司机,带我一起走
定义选择(自己,姓名):
self.passengers.append(姓名)
#这不是去幼儿园的车,我要下车。
定义删除(自己,姓名):
Self.passengers.remove(name)控制台测试:
bus1=HauntedBus([Alice , Bill])
巴士1 .乘客
[爱丽丝,比尔]
bus1.pick(查理)
bus1.drop(爱丽丝)
1.bus1 .乘客#目前没有问题。
[比尔,查理]
Bus2=HauntedBus()#起初,Bus2是空的,所以默认情况下将的空列表分配给self.passengers。
布斯2 .皮克(《嘉莉》)
公交车2 .乘客
[《嘉莉》]
Bus3=HauntedBus() #一开始也是空的,所以还是默认的赋值列表。
3.bus3.passengers #但是默认列表不为空。
[《嘉莉》]
bus3.pick(Dave )
2.登上巴士3的乘客#戴夫出现在巴士2上。
[《凯莉》,《戴夫》]
2.乘客是3路公共汽车。乘客#问题是他们指向同一个列表。
真实的
1 .乘客#但是巴士1 .乘客是一个不同的列表。
[比尔,查理]
Ps=[jack , rose] #声明一个列表Ps
bus4=HauntedBus(ps)
4.bus4.passengers #与列表中的值相同
[杰克,罗斯]
Ps.append(groves) #ps list添加新元素
4.巴士4 .乘客#也在巴士4上
[杰克,罗斯,格罗夫斯]
4.公交车。DROP( Rose )# bus 4 . DROP( Rose )# bus 4人
Ps #ps列表页缺少元素。
[jack , groves]问题是没有指定初始乘客的HantedBus实例将共享相同的乘客列表。
这是因为self.passengers已成为passengers参数默认值的别名。这个问题的根源是在定义函数的时候计算了默认值。
(通常是加载模块的时候),所以默认值就变成了函数对象的属性。因此,如
如果默认值是一个可变对象,并且它的值被修改,那么后续的函数调用将受到
去影响。
你可以看看HauntedBus。__init__对象,并在其__defaults__属性中查看那些幽灵学生:
出没巴士。__init__。__默认值_ _
([Carrie , Dave],)最后,我们可以验证bus2.passengers是绑定到
关于幽灵巴士的第一个元素。_ _ init _ _。_ _ defaults _ _属性:
出没巴士。__init__。__defaults__[0]是bus2.passengers
这个由变量默认值True引起的问题解释了为什么None通常用作接收变量值的参数的默认值。
Defense Parameters如果定义的函数接收可变参数,那么应该仔细考虑调用者是否期望修改传递的参数。
bus4=HauntedBus(ps)
4.bus4.passengers #与列表中的值相同
[杰克,罗斯]
Ps.append(groves) #ps list添加新元素
4.巴士4 .乘客#也在巴士4上
[杰克,罗斯,格罗夫斯]
4.公交车。DROP( Rose )# bus 4 . DROP( Rose )# bus 4人
Ps #ps列表页缺少元素。
[jack , groves]这个问题是因为代码self.passengers=passengers而出现的。
正确的做法是校车维护自己的乘客名单:
def __init__(self,passengers=None):
如果没有乘客:
self.passengers=[]
否则:
self . passengers=list(passengers)#通过构造函数创建一个副本。除非这个方法真的想修改通过参数传入的对象,否则它就在类中
直接给实例变量赋值参数之前一定要三思,因为这将是参数对。
比如创建一个别名。如果不确定,那么创建一个副本。这样可以让顾客少一些麻烦。
和del垃圾收集del语句删除名称而不是对象。Del命令可能会导致对象被视为垃圾。
Recycle,但是只有当被删除的变量保存了对象的最后一个引用,或者它不可用时。
当到达对象时。
为了演示对象生命周期结束时的情况,下面使用weakref.finalize注册一个回调函数,该函数在对象被销毁时被调用。
进口武器f
s1={1,2,3}
S2=s1 #s1和S2是别名,指向同一个集合。
def bye():
.印刷品(‘飘……’)
.
Ender=weakref.finalize (s1,bye) #在s1引用的对象上注册回调bye。
Ender.alive #在调用finalize对象之前,alive属性的值为True。
真实的
Del s1 #del不删除对象,但删除对象的引用。
安德,活着
真实的
S2=spam #重新绑定最后一个引用,这样{1,2,3}就无法得到它。
乱世佳人.# object被销毁,bye回调被调用。
安德,活着
真正的弱引用正是因为引用,对象才存在于内存中。当对象的参考号为零时,
垃圾收集器将销毁该对象。但是,有时候你需要引用一个对象,而不是让它存在。
的时间超过了要求的时间。这通常用于缓存。
弱引用不会增加对对象的引用数量。被引用的目标对象称为被引用对象。
(指物).因此,我们说弱引用不会阻止被引用对象被作为垃圾回收。
下面显示了如何使用weakref.ref实例来获取被引用的对象。如果是
像存在,可以通过调用弱引用获得对象;否则,不返回任何值。
a_set={0,1}
Wref=weakref.ref(a_set) #创建弱引用对象Wref
wref #a_set的弱引用对象
0x7f539f165a98处的weakref将0x7f5397bbdf28“置位”
Wref() #调用Wref()返回被引用的对象{0,1}。因为这是一个控制台会话,{0,1}被绑定到_变量。
{0, 1}
A_set={2,3,4} #a_set不再指向{0,1}集合,因此对集合的引用数量减少。但是_ variable还是引用了它。
Wref()#调用Wref()仍然返回{0,1}。
{0, 1}
Wref()是None #当计算该表达式时,{0,1}存在,因此wref()不是None。但是,then _被绑定到结果值False。现在{0,1}没有强引用。
错误的
wref()是None
TruePython控制台会自动将_变量绑定到结果不是None的表达式的结果。
WeakValueDictionary介绍WeakValueDictionary类实现了一个变量映射,其中的值是对象。
的弱引用。当被引用的对象在程序的其他地方被作为垃圾回收后,相应的
的键会自动从WeakValueDictionary中删除。因为
这里,WeakValueDictionary经常用于缓存。
实现一个简单的类来表示所有种类的奶酪。
# -*-编码:utf-8 -*
进口武器f
奶酪等级:
def __init__(self,kind):
善良=善良
def __repr__(self):
返回“Cheese(% r)% self . kind
if __name__==__main__ :
Stock=weakeref。WeakValueDictionary () #构造WeakValueDictionary的一个实例
catalog=[Cheese(Red Leicester )、Cheese(Tilsit )、Cheese(Brie )、Cheese(Parmesan)]
对于目录中的奶酪:
stock[Cheese . kind]=cheese # stock将cheese的名称映射到目录中cheese实例的弱引用。
打印(奶酪)#奶酪(“帕尔马干酪”)
print(sorted(stock . keys()))#[ Brie , Parmesan , Red Leicester , Tilsit]
Del catalog #删除目录后,库存的奶酪大部分都没了,这是WeakValueDictionary的预期行为。为什么不是全部?
print(sorted(stock . keys()))#[ Parmesan ]
德尔奶酪
Print (sorted (stock.keys ()) # []临时变量引用对象,可能导致变量持续时间比预期长。通常,这对于局部变量来说不是问题,因为当函数返回
会被摧毁。但在上面的例子中,for循环中的变量cheese是一个全局变量,除非显式删除,否则不会消失。
弱引用的局限性在于,不是每个Python对象都可以成为弱引用的目标(或被引用对象)。基础
的list和dict实例不能被称为对象,但是它们的子类可以很容易地被称为对象
要解决这个问题:
类别列表(列表):
列表的子类和实例可以用作弱引用的目标
a_list=MyList(范围(10))
# a_list可能是弱引用的目标
wref _ to _ a _ list=weak ref . ref(a _ list)set实例可以作为被引用对象,自定义类型就可以了。但是,int和tuple的实例不能是弱引用的目标,即使是它们的子类。
Python对tuple T,t[:]的不可变类型(可选读取)的技巧不是创建副本,而是返回相同的对
对图像的引用。此外,tuple(t)获取对同一元组的引用。
t1=(1,2,3)
t2=元组(t1)
t2是t1
真实的
t3=t1[:]
t3是t1
诚然,bytes和frozenset实例也有这种行为。注意,frozenset实例不是一个序列,所以不能使用fs[:] (fs是一个frozenset实例
例子)。然而,fs.copy()具有相同的效果:它会欺骗您并返回相同的结果
不创建副本:
t1=(1,2,3)
t3=(1,2,3)
t3是t1
错误的
s1=ABC
s2=ABC
S2 S1 # S1和S2指的是同一个字符串。
共享字符串文字是一种叫做interning的优化措施(这不就和Java中的常量池技术很像吗)。CPython还将在小整数上使用这种优化措施,以防止重复创建“热”数,如0,-1和42。(Java中的Integer也缓存-128到127之间的值)。
永远不要依赖于字符串或整数的存在!比较是字符串还是整数
等于,应该用==而不是is。由常驻Python解释器在内部使用。
的一个特征。
原创作品来自愤怒的可乐,的博主,转载请联系作者取得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。