摘要:运算符比较两个对象的标识函数返回对象标识的整数表示。实际上,每个对象都会统计有多少引用指向自己。对象被销毁了,调用了回调,的值变成了。当对象的引用数量归零后,垃圾回收程序会把对象销毁。引用的目标对象称为所指对象。
对象不是个盒子
class Gizmo: def __init__(self): print("Gizmo id: %d" % id(self)) x = Gizmo() print(x) y = Gizmo() * 10 print(y) print(dir())
❶ 输出的 Gizmo id: ... 是创建 Gizmo 实例的副作用。
❷ 在乘法运算中使用 Gizmo 实例会抛出异常。
❸ 这里表明,在尝试求积之前其实会创建一个新的 Gizmo 实例。
❹ 但是,肯定不会创建变量 y,因为在对赋值语句的右边进行求值时抛出了异常。
longe = {"name": "longe", "born": 1993} liang = longe print(liang is longe) print(id(liang), id(longe)) longe["balance"] = 950 print(liang) ## 冒充的longe信息 other = {"name": "longe", "born": 1993, "balance": 950} print(other) print(other is longe)
❶ liang 是 longe 的别名。
❷ is 运算符和 id 函数确认了这一点。
❸ 向 liang 中添加一个元素相当于向 longe 中添加一个元素。
在那段代码中,liang 和 longe 是别名,即两个变量绑定同一个对象。
而 other 不是 longe 的别名,因为二者绑定的是不同的对象。other 和longe 绑定的对象具有相同的值(== 比较的就是值),但是它们的标识不同。
每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;
你可以把标识理解为对象在内存中的地址。
is 运算符比较两个对象的标识;
id() 函数返回对象标识的整数表示。
在==和is之间选择== 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。
is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是 直接比较两个整数 ID
eq 方法,会考虑对象属性的值。相等性测试可能涉及大量处理工作,例如,比较大型集合或嵌套层级深的结构时。
元组的相对不可变性元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关
元组的值会随着引用的可变对象的变化而变。
元组中不可变的是元素的标识。内存地址
>>> t1 = (1, 2, [30, 40]) ➊ >>> t2 = (1, 2, [30, 40]) ➋ >>> t1 == t2 ➌ True >>> id(t1[-1]) ➍ 4302515784 >>> t1[-1].append(99) ➎ >>> t1 (1, 2, [30, 40, 99]) >>> id(t1[-1]) ➏ 4302515784 >>> t1 == t2 ➐ False
基础理解!!!还是可以的默认浅复制
>>> l1 = [3, [55, 44], (7, 8, 9)] >>> l2 = list(l1) ➊ >>> l2 [3, [55, 44], (7, 8, 9)] >>> l2 == l1 ➋ True >>> l2 is l1 ➌ False
然而,构造方法或 [:] 做的是浅复制(即复制了最外层容器,副本中的元素是源容器中
元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。
import copy 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) bus1 = Bus(["Alice", "Bill", "Claire", "David"]) bus2 = copy.copy(bus1) bus3 = copy.deepcopy(bus1) print(id(bus1), id(bus2), id(bus3)) bus1.drop("Bill") print(bus2.passengers) print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)) print(bus3.passengers)
❸ 审查 passengers 属性后发现,bus1 和 bus2 共享同一个列表对象,因为 bus2 是
bus1 的浅复制副本。
❹ bus3 是 bus1 的深复制副本,因此它的 passengers 属性指代另一个列表。
注意,一般来说,深复制不是件简单的事。如果对象有循环引用,那么这个朴素的算法会进入无限循环深复制
>>> a = [10, 20] >>> b = [a, 30] >>> a.append(b) >>> a [10, 20, [[...], 30]] >>> from copy import deepcopy >>> c = deepcopy(a) >>> c [10, 20, [[...], 30]]
深复制有时可能太深了。例如,对象可能会引用不该复制的外部资源或单例值。我们可以实现特殊方法 __copy__() 和 __deepcopy__(),控制 copy 和 deepcopy 的行为函数的参数作为引用时
共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参
是实参的别名。
def f(a, b): a += b return a a = [1, 2] b = [3, 4] print(f(a, b)) print(a, b)
这里变量全都是引用,无论局部变量还是全局.
所以上面案例中,a会变化
class HauntedBus: """备受幽灵乘客折磨的校车""" def __init__(self, passengers=[]): #别使用这种可变类型 作为默认参数 self.passengers = passengers防御性编程(对待可变类型)
class TwilightBus: """正常的校车""" 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) bus1 = TwilightBus(("sfs", "sdf")) bus2 = TwilightBus(["sdfsdfsfd111"]) bus1.pick("ppxia") bus1.drop("sfs") print(bus1.passengers) bus2.drop("sdfsdfsfd111") print(bus2.passengers)
尽量别用可变类型做默认参数值, 实在要用,必须使其产生副本del和垃圾回收
有个 del 特殊方法,但是它不会销毁实例,不应该在代码中调用。
即将销毁实例时,Python 解释器会调用 del 方法,给实例最后的机会,释放外资源。
自己编写的代码很少需要实现 del 代码,有些 Python 新手会花时间实现,但却吃力不讨好,因为 del 很难用对。
垃圾计数器在 CPython 中,垃圾回收使用的主要算法是引用计数。
实际上,每个对象都会统计有多少引用指向自己。
当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。
为了演示对象生命结束时的情形,示例 8-16 使用 weakref.finalize 注册一个回调函数,在销毁对象时调用。>>> import weakref >>> s1 = {1, 2, 3} >>> s2 = s1 ➊ >>> def bye(): ➋ ... print("Gone with the wind...") ... >>> ender = weakref.finalize(s1, bye) ➌ >>> ender.alive ➍ True >>> del s1 >>> ender.alive ➎ True >>> s2 = "spam" ➏ Gone with the wind... >>> ender.alive False
❺ 如前所述,del 不删除对象,而是删除对象的引用。
❻ 重新绑定最后一个引用 s2,让 {1, 2, 3} 无法获取。对象被销毁了,调用了 bye 回
调,ender.alive 的值变成了 False。
正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
弱引用是可调用的对象,返回的是被引用的对象;>>> import weakref >>> a_set = {0, 1} >>> wref = weakref.ref(a_set) ➊ >>> wref>>> wref() ➋ {0, 1} >>> a_set = {2, 3, 4} ➌ >>> wref() ➍ {0, 1} >>> wref() is None ➎ False >>> wref() is None ➏ True
❷ 调用 wref() 返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以 {0, 1}
会绑定给 _ 变量。
❸ a_set 不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代
它。
❹ 调用 wref() 依旧返回 {0, 1}。
❺ 计算这个表达式时,{0, 1} 存在,因此 wref() 不是 None。但是,随后 _ 绑定到结
果值 False。现在 {0, 1} 没有强引用了。
❻ 因为 {0, 1} 对象不存在了,所以 wref() 返回 None。
变量的不是盒子,是便利贴(就是c的指针)
==是值相等 is是(内存地址相等)
默认是浅复制,就内存地址复制.深复制会有一些过深危险(可以重写特殊方法 __copy__() 和 __deepcopy__())
尽量别用可变类型做默认参数值, 实在要用,必须使其产生副本
实际上,每个对象都会统计有多少引用指向自己。 Cpython中, 当引用计数归零时,对象立即就被销毁:CPython会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/41681.html
摘要:前言本文内容基本摘抄自深入理解虚拟机,以供复习之用,没有多少参考价值。此区域是唯一一个在虚拟机规范中没有规定任何情况的区域。堆是所有线程共享的内存区域,在虚拟机启动时创建。虚拟机上把方法区称为永久代。 前言 本文内容基本摘抄自《深入理解Java虚拟机》,以供复习之用,没有多少参考价值。想要更详细了解请参考原书。 第二章 1.运行时数据区域 showImg(https://segment...
摘要:通常,这种模式是通过定义一个代表处理对象的抽象类来实现的,在抽象类中会定义一个字段来记录后续对象。工厂模式使用表达式第章中,我们已经知道可以像引用方法一样引用构造函数。 一、为改善可读性和灵活性重构代码 1.改善代码的可读性 Java 8的新特性也可以帮助提升代码的可读性: 使用Java 8,你可以减少冗长的代码,让代码更易于理解 通过方法引用和Stream API,你的代码会变得更...
摘要:当引用计数为零,则不再需要该对象且可以销毁。这表明当变量被删除时引用计数正确的变为零。方法只能在循环被打破后且引用计数已经为零时调用。这两步的过程允许引用计数或垃圾收集删除已引用的对象,让弱引用悬空。这允许在方法设置对象属性值之前进行处理。 注:原书作者 Steven F. Lott,原书名为 Mastering Object-oriented Python __del__()方法 ...
摘要:函数的参数作为引用时唯一支持的参数传递模式是共享传参,它指函数的形参获得实参中各个引用的副本,即形参是实参的别名。而在上面这个例子中,类的属性实际上是形参所指向的对象所指对象,的别名。 《流畅的Python》笔记本篇是面向对象惯用方法的第一篇,一共六篇。本篇主要是一些概念性的讨论,内容有:Python中的变量,对象标识,值,别名,元组的某些特性,深浅复制,引用,函数参数,垃圾回收,de...
阅读 997·2023-04-26 02:21
阅读 2816·2021-09-24 09:47
阅读 1606·2019-08-30 15:55
阅读 2162·2019-08-30 14:01
阅读 2319·2019-08-29 14:01
阅读 2047·2019-08-29 12:46
阅读 813·2019-08-26 13:27
阅读 1932·2019-08-26 12:23