| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 流畅的python笔记(八)对象引用、可变性和垃圾回收 -> 正文阅读 |
|
[Python知识库]流畅的python笔记(八)对象引用、可变性和垃圾回收 |
目录 一、变量不是盒子python中变量不是盒子,而是盒子上贴的标签。对象才是盒子。对象一般是指一块存储空间,变量是对象上贴的标签,一个对象可以贴好多个标签,即可以对应很多个变量名。如下例子,把变量a和b分配给了同一个列表对象,类似于C++中的引用概念,python中的变量的本质都是C++中的引用。 ? 因为对象在右边,总是先于变量创建,因此正确说法是把某变量分配给某对象,而不是反过来。 二、标识、相等性和别名对象的标识、类型和值每个对象都有标识、类型和值。
== 和 is的不同== 运算符比较两个对象的值,而 is 比较对象的标识。 ? ? ? ? 一个特殊的例子,最常使用is检查变量绑定的值是不是None。
元组的相对不可变性元组里保存的是元素的引用,但如果引用的元素是可变的,即便元组本身不可变,其元素也依然可以变化。即元组中不可变的是元素的标识,而不是元素的值。 三、默认做浅拷贝浅拷贝我们知道python中的变量不是盒子,二是标签,那么当我们要复制一个列表的时候,如果直接用 l2 = l1 则相当于l2和l1是别名,两个对应同一个对象。要复制列表最简单的方式是使用其构造函数。如下面例子中,l1 和 l2的值相同,但是是两个不同的对象。 对于列表和其它可变序列来说,还可以使用 l2 = l1[ : ] 来创建副本。
根据实验结果可看出,用 l2 = l1[:] 创建副本时 l2与l1确实是不同的对象,但是由于列表对象中的元素是引用,而l2直接复制了l1中的引用,因此l1和l2中元素对应的是相同的对象。若所有元素不可变,这样做没有问题且可以节省内存,但如果有可变的元素则这样不对。 ? ? ? ? 综上,用构造方法或者[ : ]创建副本时做的都是浅拷贝。即只复制了外壳,副本中的元素都是源容器中元素的引用。 ? ? ? ? 接下来一个例子,对一个包含一个列表和一个元组的列表做浅拷贝。
? ? ? ? 上述例子输出如下,符合预期。 深拷贝深拷贝即副本不共享内部对象的引用。 ? ? ? ? 不管浅拷贝还是深拷贝,拷贝之后的副本与原对象肯定是不同的对象,即标识不同,但是浅拷贝中副本与原对象共享内部元素的引用。copy模块提供的deepcopy和copy函数能为任意对象深拷贝和浅拷贝。下面例子展示deepcopy和copy的用法。
通过特殊方法__copy__()和__deepcopy__()可以控制copy和deepcopy的行为。 ? ? ? ? 含有循环引用的拷贝问题不做讨论。 四、函数的参数作为引用python函数默认引用传参C里边默认的传参方式是值传递,而python里边默认的传参方式是引用传递,即形参是实参的别名,因此如果传入的实参是可变对象,则函数体内可能会改变实参。如下例子所示,传入不可变对象时是改变不了实参的,因为 a += b,实际上a已经分配给一个新的对象了,因此不会改变实参。 不要使用可变类型作为参数默认值函数的有些参数可以设置缺省/默认值,但是这个值不能用可变类型。 上边例子中把HauntedBus类的构造函数中passengers的默认值由None改成了空列表[ ]。带来的改变是某个HauntedBus类的对象的self.passengers属性是__init__函数的参数passengers的别名,而默认情况下,如果在构造的时候没有传参,即默认构造时,passengers时空列表[ ] 的别名。而空列表[ ] 这个时候已经变成了函数__init__的一个属性(python中函数是一等对象,因此空列表[ ]是函数对象__init__的属性这点不难理解。 ? ? ? ? 因此,当我们构造bus1时,因为传参构造,所以passengers是传入参数的别名,而非函数对象__init__的属性空列表[ ] 的别名,因此正常运行,不会有错。 ? ? ? ? 但是当我们构造bus2时,因为是默认构造,因此bus2的self.passengers实际上是函数对象__init__的属性空列表 [ ] 的别名,因此对于bus2的self.passengers属性的一切操作都是反应到函数对象__init__的属性上,因此当我们默认构造bus3的时候,实际上这个时候默认的passengers已经不是空列表了,而且bus2和bus3的self.passengers属性是同一个列表对象的别名(这个列表对象即为构造函数中的默认列表参数)。 防御可变参数如果定义的函数接收可变参数,需要谨慎考虑是否修改传入的参数。 ? 还是这个校车类,下面例子中我们传入实参basketball_team,然后经过校车类对象处理后影响到了实参,如果要避免影响到实参,在构造函数中应该给对象的self.passengers属性分配实参的一个副本,而不应该直接跟实参绑定。 ? 改造方案如下: 五、del和垃圾回收del只会删除对象的引用,而不会删除对象,但删除对象的引用可能会导致对象被删除。 python对象被删除有两种情况:
? ? ? ? 两种情况可以归结于同一种情况,即这个对象不可获取了,那么就会被当作垃圾回收。 weakref.finalize用函数bye来监视被s1绑定的对象,如果该对象被回收,则bye就会被调用,其返回值ender可以看s1绑定对象的状态,一开始的时候肯定是或者的,即 alive == True,当我们用del删除s1的时候,bye并没有被调用,因此对象没有被删除,这是因为对象还有一个引用s2,引用计数并不等于0。但是当我们将s2指向其他对象(即‘spam’)的时候,原先的对象就不可获取了,因此会被回收,也因此bye函数被调用的,而ender.alive也变成False。 ? ? ? ? 有一点问题是,我们在ender = weakref.finalize中把s1引用传给了finalize函数了,为了监控对象{1, 2,3}和调用回调,理论上是必须要引用{1,2,3}的,那么为什么最终{1,2,3}被销毁了呢?这是因为finalize持有{1,2,3}的弱引用。 六、弱引用弱引用可调用,返回所指对象?? ? 首先有一点注意,python控制台会自动把 _ 变量绑定到结果不为None的表达式结果上,因此可能会隐式地增加了新的引用。
????????weakref.ref类是低级接口,不要直接使用,应该使用weakref集合(WeakKeyDictionary、WeakValueDictionary、WeakSet)和finalize。 WeakValueDictionary实现一种可变映射,键是对象的某种可散列的属性,值值对象的弱引用。如果对象被回收,则对应的键会从WeakValueDictionary中删除。 ?
弱引用的局限不是每个python对象都可以作为弱引用的所指对象。
由于弱引用并不保证引用对象不会被回收,因此如果代码中需要用到循环引用,则可以用弱引用来实现,用弱引用可以解决循环引用不能正常垃圾回收的问题。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 21:01:40- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |