对《流畅的python》的读书笔记以及个人理解
9-16

8.1 变量不是盒子

python的变量类似于Java中的引用式变量,最好将它们理解为附加在对象上的标注。变量并不是一个盒子,盒子中装着对象内容,而应该是一个便利贴,贴在对象上的标注,变量本身不是容器。
赋值的时候,应该说“将变量分配给对象”,而不是“将对象分配给变量”,变量是虚无缥缈的东西,而对象才是真真正正存在于内存中的,有实际意义的数据。

对象在赋值之前就已经被创建了:

>>> class Gizmo:
...     def __init__(self):
...             print('Gizmo id: %d' % id(self))
>>> x = Gizmo()
Gizmo id: 2238839532232>>> y = Gizmo() *10Gizmo id: 2238839532512
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'>>> dir()
['Gizmo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'x']
>>>

从上面的代码可以清楚看出对象在赋值之前就已经创建好了,对于Gizmo类的构造方法,其中会打印出当前创建对象在内存中的id号,因此在创建对象并赋值给x的时候,会直接打印出对象的id值。而在赋值给y这个变量的时候,可以看到依然打印出了对象的id,但随后又报错了,这是因为,Gizmo()*10这行代码,Gizmo()依然会创建对象,但Gizmo对象不可能与int对象做乘法,因此报错,赋值给y也就无从说起,最后一行代码dir()打印出了当前目录存在的变量,可以看到没有y,因为在y = Gizmo()*10中,操作的熟悉是先创建Gizmo对象,再执行乘法,最后赋值,而在执行乘法的时候引发错误而终止操作,因此y这个变量并没有被创建出来。

为了理解Python的赋值语句,读代码的时候应该从右边读起,对象在右边创建或者获取,在此之后左边的变量才会绑定到对象上,这像是为对象贴上了标签,但毫无疑问的是,由于变量只是对象的标签,因此一个对象可以有多个标签,也就是一个对象可以有多个变量标注,这些变量便是别名。

9-17

8.2 标识、相等性和别名

Lewis Carroll 是Charles Lutwidge Dodgson教授的笔名,Carroll指的就是Dodgson本人


>>> charles = {'name':'Charles L. Dodgson', 'born':1832}
>>> lewis = charles
>>> lewis is charles
True
>>> id(lewis)
1574035868624
>>> id(charles)
1574035868624
>>> lewis['balance'] = 950
>>> charles
{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
>>>

首先创建一个字典对象,将变量charles绑定到这个对象上,接着lewis = charles这行代码表明lewis已经成为字典对象的另一个别名,现在charles和lewis都是指这个对象,在内存中,这两个变量共享一个对象,使用id()查看其对象的id号可以知道。而lewis[‘balance’]=950则在字典中添加了一个新的键值对,这使得用charles查看时其内容也发生了变化。
接着上面的代码:

>>> charles
{'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
>>> alex == charles
True
>>> alex is not charles
True
>>> alex is charles
False
>>>

现在,创建一个新的字典对象,用alex标识,对象的内容与charles的相同,但charles和alex所指定的对象在内存中是两个截然不同的对象,他们的内容相同,但是在内存中的地址是不一样的。
使用==运算符可以看到true,而使用is运算符则是false。可以看到, ==运算符比较的是两个对象的值,而is运算符比较的则是两个对象的标识。
关于这个标识,可以使用id()方法来查看:

>>> id(charles)
1574035868624
>>> id(alex)
1574035868696
>>>

id()方法可以查看对象在内存中的标识,可以理解为对象在内存中的地址,在Cpython中,id()方法就是返回对象地址的整数表示,而is比较的,正是这个id()方法返回的标识。

8.2.1 在==和is之间选择

==运算符比较两个对象的值(对象中的数据),而is比较对象的标识。这有点像C++对象,两个C++对象做比较,重载运算符= =方法一般直接比较对象中的值,而使用两个分别指向这两个对象的指针,指针之间比较的则是地址值。
在变量和单例值之间的比较时,应该使用is。最常见的is检查莫过于判断变量绑定的值是不是None。

x is None

否定的写法:

x is not None

is 运算符不能重载,它的速度要比== 运算符快,而 ==运算符是由类中的__eq__()方法实现,也就是说,a == b这种代码,实际上是:

a.__eq__(b)

所有类的eq()方法都继承于object类,这个方法可以被覆盖。
需要注意的是,基本类型(int, float, str…)的变量直接与底层的数据结构挂钩,只要两个变量标识的数据值相同,那么他们的id值也是相同的,即使用is会返回True:

>>> x = 'h'
>>> y = 'h'
>>> x is y
True
>>> x == y
True

8.2.2 元组的相对不可变性

都知道元组不可修改,但这并不完全正确。
元组中的元素保留的是对象的引用,也就是说(1,2,[1,2,3])这个元组中,元素1,元素2,元素[1,2,3]其实都只是引用,并不是真正的数据,这一点造成了元组的相对不可变性,元组的不可变性其实指的是元组数据结构中保留的引用不会变,但是引用真正指向的对象变化,跟元组本身没有关系:

>>> t1 = (1,2,[1,2,3])
>>> t2 = (1,2,[1,2,3])
>>> t1 == t2
True

可以看到t1跟t2做比较,结果是True,因为检查了元组所有元素指向对象的内容,得到True。

>>> t1[-1] = 3
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>>

现在尝试对t[-1]做修改,结果自然是引发了异常,因为元组需要维护它保留的元素的不变性,也就是说,t[-1]实际上是[1,2,3]这个list对象的引用,并不是list对象本身,我姑且用t_three这个引用来指代[1,2,3]这个对象,那么元组t1就是(1,2,t_three),元组维护的元素不变性,只是维持t_three这个引用\变量不被其他的变量改变,比如变成了t_four,这个是元组不允许的,但是元组t1对于[1,2,3]这个List对象,则没有权限控制它不做改变,比如说:

>>> t1[-1].append(4)
>>> t1
(1, 2, [1, 2, 3, 4])

现在可以看到t1的内容其实是被修改的了,但是这仅仅是对列表元素的修改,对于元组本身,它维护的内容依然是各个对象的引用(1,2,t_three)。

默认做浅复制

复制列表(或者多数内置的可变集合)最简单的方式是使用内置的类型构造方法。例如:

>>> l1 = [3,[55,44],(7,8,9)]
>>> l2 = list(l1)
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l2 == l1
True
>>> l2 is l1
False

list(l1)这一句是对列表做的浅复制,在内存中重新创建一个对象,新的对象绑定了变量l2,两个对象的内容相同,地址不同,因此使用 == 会返回True,使用is返回False。
但是,这种浅复制存在一些问题,对于列表元素,简单类型(如int,float,str)在做浅复制时会重新在内存中创建出另一个简单类型的对象,但是对于list,set,tuple等内置复杂类型或者其他自定义类来讲,在执行list(l2)这种浅复制时,并不会也像简单类型那样创建出新的对象再进行绑定。换句话说,在上面的代码里,l1和l2中的list和tuple元素使用的是同一个元素,而int元素则是不同的元素。

在l1中,列表元素[55,44]属于可变型元素,这样做浅复制,可能会导致一些意想不到的问题,下面对l1和l2做操作:

>>> l1
[3, [55, 44], (7, 8, 9)]
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l1.append(100)
>>> l1[1].remove(55)
>>> l1
[3, [44], (7, 8, 9), 100]
>>> l2
[3, [44], (7, 8, 9)]
>>>

初始的时候,l1和l2内容完全相同,这没什么问题,但是l1.append(100)开始对l1添加一个尾部元素,而在l1[1].remove(55)中又将l1中的列表元素移除了55,前面说过,复杂类型的元素在做这种浅复制时并不会重新创建对象,而是采取共享的措施,这就出现了问题,在对基本元素操作时,由于不是共享,因此对一个变量的操作不会影响到另一个变量所代表的对象,但是对于复杂类型,它们采取了共享措施,对一个对象复杂类型元素的操作会影响到另一个对象中的内容,l1[1].remove(55)就是这样,内部的列表[55,44]由两个对象共享,这样一来,无论哪个对象对这个列表做操作,都会影响到另一个对象的内容。
那么,其中的元组元素又是什么情况?
l1[2]是一个元组,它是不可变的对象,同时又是复杂类型,来看看对它的操作结果如何:

>>> l1
[3, [44], (7, 8, 9), 100]
>>> l2
[3, [44], (7, 8, 9)]>>> l1[2] += (10, 11)
>>> l1
[3, [44], (7, 8, 9, 10, 11), 100]
>>> l2
[3, [44], (7, 8, 9)]
>>>

首先要清楚l1[2] += (10, 11)这个操作到底做了什么。
这个操作是合法的,重新创建了一个元组,这个元组内容是(7,8,9,10,11),然后绑定到l1上,原来l1绑定的元组被丢弃掉,那么现在l1和l2的元组元素可就不是共享的了。在这之前,它们也是共享的,但由于元组并不能修改,我们不能写出l1[2][1] = 5这种类型的代码来,因为元组维持的相对不变性不允许这种操作,而在使用+=这种操作之后,可以看到l1和l2的内容并没有相互混淆,这就是不可变元素带来的好处了,虽然他们在内部仍然是共享的,但是当试图对这些不可变元素做修改的时候,python就会强制让它们不共享,重新创建出对象出来,因此不会互相影响,所以,对于不可变对象来说,浅复制是没有问题的,而且还能节省内存资源,但是对于可变对象来说,浅复制就不是那么安全了,要想做到可变对象的安全复制(复制后对于一个对象的操作不会影响到另一个对象的内容),就要使用深层复制。

为任意对象做深层复制和浅复制

浅复制机制没什么问题,但有时需要用到深复制(即副本不共享内部对象的引用)。内置模块copy提供的copy()和deepcopy()可以实现为任意对象的浅复制和深复制。

9-20

定义一个简单类来演示copy和deepcopy的用法:

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类对象创建时,会在内部创建一个list,list属于复杂对象,而且是可变对象,在做浅复制时并不安全。

>>> bus1 = Bus(['one', 'two', 'three'])   #创建对象bus1
>>> bus2 = copy.copy(bus1)    #浅复制bus1
>>> bus3 = copy.deepcopy(bus1)  # 深复制bus1
>>> id(bus1), id(bus2), id(bus3)  # 查看id,这是三个不同的对象
(2111764720832, 2111761750392, 2111762678112)>>> bus1.drop('three')  #在bus1中移除元素three
>>> bus1.passengers  #可以看到bus1的passengers少了three
['one', 'two']
>>> bus2.passengers  # 但是对bus1浅复制的bus2的passengers也受到了影响
['one', 'two']
>>> bus3.passengers  # 而深复制的bus3不会受到影响
['one', 'two', 'three']>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
>>> # 分别查看三个对象的passengers,可以看到bus1和bus2共享同一个passengers
(2111761482312, 2111761482312, 2111760949832)

有时候深层复制可能复制的太深,一些需要共享的对象也被复制过来,这可以通过实现特殊方法__copy__()__deepcopy()__来控制copy和deepcopy的行为,链接:https://docs.python.org/3/library/copy.html中有更多详细信息。

先到这里,第八章内容有些多

第八章《对象引用、可变性和垃圾回收》(上)相关推荐

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  9. CHAR.VIII 对象引用、可变性和垃圾回收

    CHAR.VIII 对象引用.可变性和垃圾回收 本章讨论对象标识.值和别名等概念.随后,本章会揭露元组的一个神奇特性:元组是不可变的,但是其中的值是可变的,之后就引申到浅复制和深复制.接下来的话题是引 ...

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

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

最新文章

  1. 任正非最新讲话:华为专家队伍怎么建?
  2. 活动目录实战系列二(主域控无法正常启动)
  3. currentThread()方法的作用
  4. SwiftUI之深入解析布局如何自定义AlignmentGuides
  5. 为什么SimpleDateFormat不是线程安全的?
  6. 电商如何“链”动新春,看聚划算助力“原年人”的好日子
  7. seaborn—seaborn.boxplot绘制箱型图
  8. windows下定时任务设置
  9. 杭电oj —— 2052
  10. 极域电子教室功能讲解-电子教室
  11. win10五分钟自动锁屏
  12. win vista/win 7/win 2008 超级激活工具
  13. 依山傍水,云淡风轻-----只是传说
  14. 关于计算机的教学论文,关于计算机教学论文
  15. KeyShot 实时光线追踪三维渲染软件
  16. 仿射密码加密解密实现
  17. 分享Silverlight/Windows8/WPF/WP7/HTML5一周学习导读(5月21日-5月26日)
  18. 【Android】【版本适配】Android11权限适配终极解决方案
  19. 【原创】 ES5高效封装WIN10系统教程2020系列(五)常用软件安装及设置
  20. 实现PHP爬虫小技巧

热门文章

  1. Ajax请求,跨域小坑
  2. 《微信企业号开发日志》之企业号接入
  3. CentOS 6.5系统安装配置图解教程(详细图文)
  4. INFO:InstallShield InstallScript工程中自定义界面文本输入控件的两个注意事项
  5. 从索引 0 处开始,初始化字符串的格式不符合规范。
  6. [文摘20070913]最好的消息
  7. 计算机中位运算的一些性质与技巧
  8. [云炬ThinkPython阅读笔记]1.7 调试
  9. 主成分分析的数学原理
  10. 静态链接中的那点事儿(2):C++二进制兼容性及跨平台初步