Python的垃圾回收机制,

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: