《流畅的Python》卢西亚诺·拉马略 第8章 读书笔记

8.1 变量不是盒子

为了理解Python中的赋值语句,应该始终先读右边。对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。
因为变量只不过是标注,所以无法阻止为对象贴上多个标注。

8.2 标识、相等性和别名

每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;你可以把标识理解为对象在内存中的地址。
==运算符比较两个对象的值(对象中保存的数据)
is比较对象的标识,id()函数返回对象标识的整数表示

最常使用is检查变量绑定的值是否为None
x is None​​
x is not None​​

is运算符比==速度快,因为它不能重载,所以Python不用寻找并调用特殊方法,而是直接比较两个整数ID。而a==b是语法糖,等同于a.__eq__(b)。继承自object的__eq__方法比较两个对象的ID,结果与is一样。但是多数内置类型使用更有意义的方式覆盖了__eq__方法,会考虑对象属性的值。

8.3 默认做浅复制

构造方法或[:]做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题。

为任意对象做深复制和浅复制
有时我们需要的是深复制(即副本不共享内部对象的引用)。copy模块提供的deepcopy和copy函数能为任意对象做深复制和浅复制。为了演示copy() 和deepcopy() 的用法,下例定义了一个简单的类,Bus。这个类表示运载乘客的校车,在途中乘客会上车或下车。
【例】校车乘客在途中上车和下车
​​

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)

交互式控制台中 创建一个Bus实例和两个副本

>>> import copy
>>> from Bus import Bus
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
>>> bus2 = copy.copy(bus1)
>>> bus3 = copy.deepcopy(bus1)
>>> id(bus1), id(bus2), id(bus3)
(140137100109976, 140137100110144, 140137100110312)
>>> bus1.drop('Bill')
>>> bus1.passengers
['Alice', 'Claire', 'David']
>>> bus2.passengers
['Alice', 'Claire', 'David']
>>> bus3.passengers
['Alice', 'Bill', 'Claire', 'David']
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
(140137100086024, 140137100086024, 140137136242952)
>>>

说明:
line4, bus2是bus1的浅复制
line5, bus3是bus1的深复制
line8, bus1中的'Bill'下车
line12, bus2中也没有'Bill'
line15, 审查passengers属性后发现,bus1和bus2共享同一个列表对象

8.4 函数的参数作为引用时

8.4.1 不要使用可变类型作为参数

以例1为基础定义一个新类HauntedBus,然后修改__init__方法。passengers的默认值不是None,而是[],这样就不用像之前那样使用if判断了。这个“聪明的举动”会让我们陷入麻烦。
【例2】一个简单的类,说明可变默认值的危险

class HauntedBus:"""备受幽灵乘客折磨的校车"""def __init__(self, passengers=[]):self.passengers = passengersdef pick(self, name):self.passengers.append(name)def drop(self, name):self.passengers.remove(name)

line3  如果没传入passengers参数,使用默认绑定的列表对象,一开始是空列表。
line4  这个赋值语句把self.passengers变成passengers的别名,而没有传入passengers参数时,self.passengers是默认列表的别名。
line6、8  在self.passengers上调用.append()和.remove()方法时,修改的其实是默认列表,它是函数对象的一个属性。
HauntedBus的诡异行为 -->

>>> from Bus import HauntedBus
>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.passengers
['Alice', 'Bill']
>>> bus1.pick('Charlie')
>>> bus1.drop('Alice')
>>> bus1.passengers
['Bill', 'Charlie']
>>>
>>> bus2 = HauntedBus()
>>> bus2.pick('Carrie')
>>> bus2.passengers
['Carrie']
>>>
>>> bus3 = HauntedBus()
>>> bus3.passengers
['Carrie']
>>> bus3.pick('Dave')
>>> bus3.passengers
['Carrie', 'Dave']
>>>
>>> bus2.passengers
['Carrie', 'Dave']
>>> bus2.passengers is bus3.passengers
True
>>> bus1.passengers
['Bill', 'Charlie']
>>>

说明:

line7 目前没什么问题,bus1没有出现异常。
line10 一开始,bus2是空的,因此把默认的空列表赋值给self.passengers。
line15 bus3一开始也是空的,因此还是赋值默认的列表。
line16 但是默认列表不为空!
line22 登上bus3的Dave出现在bus2中。
line24 问题是,bus2.passengers和bus3.passengers指代同一个列表。
line26 但bus1.passengers是不同的列表。

问题在于,没有指定初始乘客的HauntedBus实例会共享同一个乘客列表。
实例化HauntedBus时,如果传入乘客,会按预期运作。
但是不为HauntedBus指定初始乘客的话,self.passengers变成了passengers参数默认值的别名。

审查HauntedBus.__init__对象,看看它的__defaults__属性中的那些幽灵学生:

>>> dir(HauntedBus.__init__)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>>
>>> HauntedBus.__init__.__defaults__
(['Carrie', 'Dave'],)
>>>

我们可以验证bus2.passengers是一个别名,它绑定到HauntedBus.__init__.__defaults__属性的第一个元素上:

>>> HauntedBus.__init__.__defaults__[0] is bus2.passengers
True
>>>

可变默认值导致的这个问题说明了为什么通常使用None作为接收可变值的参数的默认值
在例1中,__init__方法检查passengers参数的值是否为None,如果是就把一个新的空列表赋值给self.passengers。如果passengers不是None,正确的实现会把passengers的副本赋值给self.passengers。

8.4.2 防御可变参数

在__init__中,传入passengers参数时,应该把参数值的副本赋值给self.passengers

除非这个方法确实想修改通过参数传入的对象,否则在类中直接把参数赋值给实例变量之前一定要三思,因为这样会为参数对象创建别名。

8.5 del和垃圾回收

del语句删除名称(对象的引用),而不是对象。
对象才会在内存中存在是因为有引用。当对象的引用数量归零后,垃圾回收程序会把对象销毁。

仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时,del命令会导致对象被当作垃圾回收。
重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。

8.6 弱引用

有时需要引用对象,而不让对象存在的时间超过所需时间,这经常用在缓存中。
弱引用不会增加对象的引用数量  ==>  弱引用不会妨碍所指对象被当作垃圾回收

引用的目标对象称为所指对象(referent),如果所指对象不存在了,返回None

>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set)   #创建弱引用对象wref
>>> wref
<weakref at 0x7f86f289af98; to 'set' at 0x7f86f0b799e8>
>>> wref()  #调用wref()返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以{0, 1}会绑定给_变量
{0, 1}
>>> _
{0, 1}
>>>
>>> a_set = {2, 3, 4}   #a_set不再指代{0, 1}集合,因此集合的引用数量减少了。但是_变量仍然指代它。
>>>
>>> _
{0, 1}
>>> wref()   #调用wref()依旧返回{0, 1}。
{0, 1}
>>>
>>> wref() is None   #计算这个表达式时,{0, 1}存在,因此wref()不是None。
False
>>> _   #随后_绑定到结果值False。现在{0, 1}没有强引用了。
False
>>>
>>> wref() is None   #因为{0, 1}对象不存在了,所以wref()返回None。
True
>>>

说明:多数程序最好使用WeakKeyDictionary、WeakValueDictionary、WeakSet和finalize(在内部使用弱引用),不要自己动手创建并处理weakref.ref实例。

8.6.1 WeakValueDictionary简介

WeakValueDictionary类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象在程序中的其它地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary中删除。因此,WeakValueDictionary经常用于缓存。

class Cheese:def __init__(self, kind):self.kind = kinddef __repr__(self):return 'Cheese(%r)'%self.kind

把catalog中的各种奶酪载入WeakValueDictionary实现的stock中。删除catalog后,stock中只剩下一种奶酪了。你知道为什么帕尔马干酪(Parmesan)比其他奶酪保存的时间长吗?代码后面的提示中有答案。
交互式环境测试:

>>> from Cheese import Cheese
>>> import weakref
>>> stock = weakref.WeakValueDictionary()   #stock是WeakValueDictionary实例
>>> catalog = [Cheese('Red Leicester'), Cheese('Tilsit'),Cheese('Brie'), Cheese('Parmesan')]
>>> for cheese in catalog:
...     stock[cheese.kind] = cheese   #stock把奶酪的名称映射到catalog中Cheese实例的弱引用上
>>> sorted(stock.keys())
['Brie', 'Parmesan', 'Red Leicester', 'Tilsit']   #stock是完整的。
>>> del catalog
>>> sorted(stock.keys())
['Parmesan']   #删除catalog之后,stock中的大多数奶酪都不见了,这是WeakValueDictionary的预期行为。为什么不是全部呢?
>>>
>>> del cheese
>>> sorted(stock.keys())
[]
>>>

临时变量引用了对象,这可能会导致该变量的存在时间比预期长。通常,这对局部变量来说不是问题,因为它们在函数返回时会被销毁。但上述举例中,for循环中的变量cheese是全局变量,除非显式删除,否则不会消失。

与WeakValueDictionary对应的是WeakKeyDictionary,后者的键是弱引用。

8.6.2 弱引用的局限

不是每个Python对象都可以作为弱引用的目标(或称所指对象)。
基本的list和dict实例不能作为所指对象,但是它们的子类可以轻松地解决这个问题;
set实例可以作为所指对象,用户定义的类型也没问题;
int和tuple实例不能作为弱引用的目标,甚至它们的子类也不行。

[Python] 对象引用、可变性和垃圾回收相关推荐

  1. 流畅的python 对象引用 可变性和垃圾回收

    对象引用.可变性和垃圾回收 变量不是盒子 人们经常使用"变量是盒子"这样的比喻,但是这有碍于理解面向对象语言中的引用式变量.Python 变量类似于 Java 中的引用式变量,因此 ...

  2. 记录学习《流畅的python》的一些知识-----对象引用,可变性和垃圾回收

    记录我学习<流畅的python>的过程--对象引用,可变性和垃圾回收 2021.9.22 1.变量不是盒子 2.标识.相等性和别名 3.默认做浅复制 4.函数的参数作为引用时 5.del和 ...

  3. 《Fluent Python》学习笔记:第 8 章 对象引用、可变性和垃圾回收

    本文主要是 Fluent Python 第 8 章的学习笔记.这部分主要是介绍了变量.引用.对象.深拷贝.浅拷贝.垃圾回收等.本章虽然枯燥,但是非常有用. <Fluent Python>学 ...

  4. [流畅的Python][8][对象引用、可变性和垃圾回收]

    第8章 对象引用.可变性和垃圾回收 "你不开心,"白骑士用一种忧虑的声调说,"让我给你唱一首歌安 慰你吧--这首歌的曲名叫作 :<黑线鳕的眼睛>." ...

  5. 流畅的Python读书笔记-第八章-对象引用、可变性和垃圾回收

    第8章:对象引用,可变性和垃圾回收 在Python里面变量不是盒子,而是便利贴,类似于Java中的引用变量,因此最好把它们理解为附加在对象上的标注. 因为变量不过是标注,因此无法阻止为对象贴上多个标注 ...

  6. python基础编程:基于Python对象引用、可变性和垃圾回收详解

    下面小编就为大家带来一篇基于Python对象引用.可变性和垃圾回收详解.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 变量不是盒子 在示例所示的交互式控制台中,无法使用 ...

  7. 第八章《对象引用、可变性和垃圾回收》(下)

    对<流畅的python>的读书笔记以及个人理解 9-20 接着上一篇的内容,上一篇的链接:https://blog.csdn.net/scrence/article/details/100 ...

  8. Fluent_Python_Part4面向对象,08-ob-ref,对象引用、可变性和垃圾回收

    第四部分第8章,对象引用.可变性和垃圾回收 1. 创建对象之后才会把变量分配给对象 变量是对象的标注,是对象的别名,是对象的引用,并不是对象存储的地方. 例子1. 证明赋值语句的右边先执行 class ...

  9. 第8章 对象引用、可变性和垃圾回收

    1 #<流畅的Python>读书笔记 2 # 第四部分 面向对象惯用法 3 # 第8章 对象引用.可变性和垃圾回收 4 5 # 8.1 变量不是盒子 6 # Python 变量类似于 Ja ...

  10. 对象引用、可变性和垃圾回收

    对象引用.可变性和垃圾回收 变量不是盒子 变量不是盒子,而是便利贴,python是先创建对象然后再将变量赋值给对象,当创建对象之后,可以通过id查看对象的内存地址. 例如: class Gizmo:d ...

最新文章

  1. Lua截取utf-8编码的中英文混合字符串
  2. Ceryx —— 基于 OpenResty 的动态反向代理
  3. shell之a+b求和l脚本的三种写法
  4. 学java的困惑_学习Java - 关于一些代码困惑
  5. ARM-Linux中断系统
  6. Centos-redis安装配置
  7. IDEA整合Spring Boot项目访问jsp文件
  8. 计算机刷bios版本,详细教你电脑刷bios
  9. 瓷砖铺贴方法_师傅总结的12种瓷砖铺贴方式,别让瓷砖毁了你的家!
  10. 可行性研究报告怎么写?
  11. 计算机一级证件照尺寸,照相馆不会告诉你的哪些事:常用证件照尺寸汇总
  12. 一分钟快速理解:模拟信号和数字信号!
  13. docker java -jar_使用Docker安装Java镜像运行jar包方法
  14. Unity Playing模式下鼠标点击放置预制体
  15. IIS6.0+Tomcat整合(java,C++等教程免费下载)
  16. php输入框里的提示文字,input标签输入框带提示文字方法
  17. 求符合给定条件的整数集(做题)
  18. 人类又被AI碾压,这次是星际争霸
  19. NASA绘制黑洞图像 高清版NASA绘制黑洞图像来了 这次终于看清了
  20. mindmanager2012打开文件出现runtime error r6025 解决方式

热门文章

  1. 计算机视觉:Opencv图像去畸变
  2. 如何用python执行macro_python与macro宏调用
  3. c语言实例--判定三角形的类型
  4. Markdown Graph
  5. 无限试用30天phpstorm
  6. informatica 遇到ORA-26002
  7. 再有三天就国庆节啦!!!~~~~~~~~~~~
  8. SSL证书无效是什么原因
  9. 2022年湖北省武汉市隐形冠军工程工业企业奖励申报条件、材料及流程
  10. 史密斯卧推:杠铃史密斯下斜卧推、上斜机卧推、平板卧推动作图解