python3[进阶]8.对象引用、可变性和垃圾回收

文章目录

    • 8.1变量不是盒子
    • 8.2 标识,相等性和别名
      • 8.2.1 在==和is之间选择
      • 8.2.2 元组的相对不可变性
    • 8.3 默认做浅复制
      • (拓展)为任意对象做深复制和浅复制
      • 深拷贝和浅拷贝有什么具体的区别呢?
    • 8.4 函数的参数作为引用时
      • 8.4.1 不要使用可变类型作为参数的默认值
    • 总结(阅读)
      • 8.4.2 防御可变参数
    • 8.5 del和垃圾回收
    • 8.6 弱引用

8.1变量不是盒子

python变量类似于Java中的引用型变量,因此最好把他们理解为附注在对象上的标注.

a = [1,2,3]
b = a
a.append(7)
print(b)

输出为:

[1, 2, 3, 7]
// 可以发现,a和b引用同一个列表,而不是那个列表的副本

在这里插入图片描述

因为变量只不过是标注,所以可以为对象贴上多个标注,贴的多个标注就是别名.

8.2 标识,相等性和别名

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

标识最常使用is运算符检查,而不是直接比较ID.

charles = {'name':'charles', 'born':'1832'}
lewis = charles
print(lewis is charles)
print(id(charles), id(lewis))
lewis['balence'] = 950
print(charles)
alex = {'name': 'charles', 'born': '1832', 'balence': 950}
print(alex == charles)
print(alex is charles)

输出:

True
140640659352168 140640659352168
{'name': 'charles', 'born': '1832', 'balence': 950}
True //比较两个对象,结果相同,这是因为dic类的__eq__方法就是这样实现的
False //但是他们是不同的对象,标识不同.
//可以发现,charles和lewis绑定同一个对象,alex绑定另外一个对象

在这里插入图片描述

8.2.1 在==和is之间选择

==运算符比较两个对象的值(对象中保存的数据),而is比较对象的标识(标识就是在内存中的位置)
在变量和"单例值"之间比较时,应该使用is.可以使用is检查变量绑定的值是不是None.

 x is None

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

8.2.2 元组的相对不可变性

元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。而 str、bytes 和 array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。
元组的不可变性其实是指tuple数据结构的物理内容(保存的引用)不可变,与引用的对象无关.
复制对象时,相等性和一致性之间的区别有更深入的影响。副本与源对象相等,但是ID不同。可是,如果对象中包含其他对象,那么应该复制内部对象吗?可以共享内部对象吗?这些问题没有唯一的答案。

8.3 默认做浅复制

l1 = [3, [55, 44], (7, 8, 9)]
l2 = list(l1)
print(l2)
print(l2 == l1)
print(l2 is l1)
print(l2[2] is l1[2])
l3 =l1[:]
print(l3)
print(l3 == l1)
print(l3 is l1)
print(l3[2] is l1[2])
l4 = l1
print(l4 == l1)
print(l4 is l1)
l1[1].append(66)
print(l1)
print(l2)
print(l3)
print(l4)

输出结果如下:
在这里插入图片描述
上图发现:

  • l2和l3对应着关于l1的浅拷贝,l4直接将l1起了一个别名,也就是说l4和l1指向了同一个对象。
  • 浅拷贝对于内层引用有影响,即内层引用还是指向了同一个对象。
  • [3, [55, 44], (7, 8, 9)] //list(l1)创建l1的副本
    True //副本和源列表相等

对于列表和其他可变序列来说,还可以使用更简洁的l3 = l1[:]语句来创建副本
然而,构造方法或者[:] 做的是浅复制(就是复制了最外层容器,副本中的元素是源容器中元素的引用).如果所有的元素都是不可变的,那么这样没有问题,如果有可变的元素,会出现问题.

 l1 = [3,[66,55,44],(7,8,9)]
l2 = list(l1)
l1.append(100)
l1[1].remove(55)
print('l1:',l1)
print('l2:',l2)
l2[1]+=[33,22]
l2[2]+=(10,11)
print('l1:',l1)
print('l2:',l2)

输出:

l1: [3, [66, 44], (7, 8, 9), 100]
l2: [3, [66, 44], (7, 8, 9)]
l1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] 
# 对**元组**来说,+=运算符创建一个新元组,然后重新绑定给变量l2[2].现在l1和l2中最后位置上的元组不是同一个对象.
# 总结:对+=和×=所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象;如果是可变对象,会就地修改。

如图:
在这里插入图片描述

(拓展)为任意对象做深复制和浅复制

浅复制没什么问题,但有时我们需要的是深复制(即副本不共享内部对象的引用)。

import copy
class Bus:def __init__(self, passengers=None):if passengers is None:self.passengers = []else:self.passengers = passengersdef 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)

输出:

140694379943920 140694379943976 140694379944088
['Alice', 'Claire', 'David']
140694377362824 140694377362824 140694377336776
['Alice', 'Bill', 'Claire', 'David']

结果如图:
在这里插入图片描述
使用 copy 和 deepcopy,创建 3 个不同的 Bus 实例。
审查 passengers 属性后发现:

  • bus1 和 bus2 共享同一个列表对象,因为 bus2 是bus1 的浅复制副本。
  • bus3 是 bus1 的深复制副本,因此它的 passengers 属性指代另一个列表。
    从上面可以发现,深拷贝和浅拷贝都会创建不同的对象,深拷贝是完全拷贝一个新的对象,浅拷贝不会拷贝子对象。

深拷贝和浅拷贝有什么具体的区别呢?

import copy
a = [1,2,3,['a','b','c']]
b = copy.copy(a)
c = copy.deepcopy(a)
a[3].append('d')
print(a)
print(b)
print(c)

结果如图:
在这里插入图片描述
从上图我们可以发现,

  • copy.deepcopy()会完全拷贝一个新的对象出现;
  • copy.copy()不会拷贝其子对象,也就是说,如果原来的对象里面又包含别的对象的引用,则这个新的对象还是会指向这个旧的内层引用。
    总结:
    copy.copy() 浅复制,不会拷贝其子对象,修改子对象,将受影响 .
    copy.deepcopy() 深复制,将拷贝其子对象,修改子对象,将不受影响.

8.4 函数的参数作为引用时

python唯一支持的参数传递模式是共享传参(call by sharing).
共享传参指函数的各个形式参数获得实参中各个引用的副本,也就是说,函数内部的形参是实参的别名。
这种方案的结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象)。

def f(a,b):a += breturn a
x,y = 1,2
print(f(x, y))
print(x, y)
a = [1, 2]
b = [3, 4]
print(f(a, b))
print(a, b)
t = (10, 20)
u = (30, 40)
print(f(t, u))
print(t, u)

输出:

3
1 2  
[1, 2, 3, 4]
[1, 2, 3, 4] [3, 4]
(10, 20, 30, 40)
(10, 20) (30, 40)

我们发现数字x没变,列表a变了,元组t没变

8.4.1 不要使用可变类型作为参数的默认值

可选参数可以有默认值,这是python函数定义的一个很好的特性.但是我们应该避免使用可变的对象作为参数的默认值.

class HauntedBus:"""备受幽灵乘客折磨的校车"""def __init__(self, passengers=[]):self.passengers = passengersdef pick(self,name):self.passengers.append(name)def drop(self,name):self.passengers.remove(name)bus1 = HauntedBus(['Alice','Bill'])
print(bus1.passengers)
bus1.pick('Charlie')
bus1.drop('Alice')
print(bus1.passengers)
bus2 = HauntedBus()
bus2.pick('Carrie')
print(bus2.passengers)
bus3 = HauntedBus()
print(bus3.passengers)
bus3.pick('Dive')
print(bus2.passengers)
print(bus2.passengers is bus3.passengers)
print(bus1.passengers)

输出:

['Alice', 'Bill']
['Bill', 'Charlie']
['Carrie']
['Carrie']//bus3一开始是空的,但是默认列表却不为空
['Carrie', 'Dive']
True
['Bill', 'Charlie']

问题在于,没有指定初始乘客的HauntedBus实例会共享同一个乘客列表。
使用可变类型作为函数参数的默认值有危险,因为如果就地修改了参数,默认值也就变了,这样会影响以后使用默认值的调用。
修正的方法很简单:在__init__方法中,传入passengers参数时,应该把参数值的副本赋值给self.passengers,

def __init__(self,passengers =None):if passengers is None:self.passengers = []else:self.passengers = list(passengers)

总结(阅读)

  1. 关于+和extend()方法,+是创建了新对象,extend是就地连接。
  2. 浅复制分为两类:
    • 第一类:t2 = t1[:]或者 t2 = list(t1) 都是了新的对象t2.但是内层引用还是指向同一个对象。
    • 第二类:l2 = copy.copy(l1)
  3. 深复制: l3 = copy.deepcopy(l1) 完全拷贝了一个新的对象。
  4. 关于增量运算符+=和×=,以+=为例:a+=b,若+=前面的a为可变序列(例如list),则就地解决,若a为不可变序列(例如tuple),则会创建新的对象。
  5. 实例中图片经过:http://www.pythontutor.com/ 生成

8.4.2 防御可变参数

如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。
示例 8-15 一个简单的类,说明接受可变参数的风险

class TwilightBus:"""让乘客销声匿迹的校车"""def __init__(self, passengers=None):if passengers is None:self.passengers = []else:self.passengers = passengers def pick(self, name):self.passengers.append(name)def drop(self, name):self.passengers.remove(name)

测试一下

>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat'] 
>>> bus = TwilightBus(basketball_team) 
>>> bus.drop('Tina') 
>>> bus.drop('Pat')
>>> basketball_team 
['Sue', 'Maya', 'Diana']

发现:下车的学生从篮球队中消失了!
TwilightBus 违反了设计接口的最佳实践,即“最少惊讶原则”。学生从校车中下车后,她的名字就从篮球队的名单中消失了,这确实让人惊讶。

这里的问题是,校车为传给构造方法的列表创建了别名。正确的做法是,校车自己维护乘客列表。修正的方法很简单:在 init 中,传入 passengers 参数时,应该把参数值的副本赋值给 self.passengers,像示例 8-8 中那样做(8.3 节)。

def __init__(self, passengers=None):
if passengers is None:self.passengers = []
else:self.passengers = list(passengers) ➊

➊ 创建 passengers 列表的副本;如果不是列表,就把它转换成列表。在内部像这样处理乘客列表,就不会影响初始化校车时传入的参数了。此外,这种处理方式还更灵活:现在,传给 passengers 参数的值可以是元组或任何其他可迭代对象,例如set 对象,甚至数据库查询结果,因为 list 构造方法接受任何可迭代对象。

8.5 del和垃圾回收

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

在 CPython 中,垃圾回收使用的主要算法是引用计数。实际上,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用__del__ 方法(如果定义了),然后释放分配给对象的内存。
CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即
使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用__del__ 方法。

8.6 弱引用

正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/273853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python (第八章)补充-可迭代对象(补充高阶函数,以及常用的高阶函数)

文章目录可迭代对象迭代器什么是迭代器什么是生成器生成器的作用生成器的注意事项总结:高阶函数什么是高阶函数?map()函数filter()函数reduce()函数参考可迭代对象 我们已经知道,可以直接作用于for循环的数据类型有以下几种: 一类…

网络阅读开篇

网络阅读也符合马太效应,投入的时间越多,获取的有效信息却越来越少,因此做出以下规定: 1、限制网络阅读时间; 2、每次阅读做总结。 本来想的挺简单的,随便搜了一下,居然一部小心拜读了两位大神的…

python (第二章)数据结构

文章目录2.5 对序列使用 和 建立由列表组成的列表2.6序列的增量赋值(和)关于 的谜题补充:extend()方法和有什么区别呢?2.7 list.sort方法和内置函数sorted(排序)2.8 用bisect来管理已排序的序列2.8.2用bisect.insort插入元素2.9 当…

[Windows Phone] 实作不同的地图显示模式

[Windows Phone] 实作不同的地图显示模式 原文:[Windows Phone] 实作不同的地图显示模式前言 本文章主要示范如何让地图有不同的模式产生,例如平面图、地形图、鸟瞰图、鸟瞰图含街道等。 这部分主要是调整 Map.CartographicMode 属性,其中 MapCartograph…

[STemWin教程入门篇]第一期:emWin介绍

特别说明:原创教程,未经许可禁止转载,教程采用回复可见的形式,谢谢大家的支持。 armfly-x2,x3,v2,v3,v5开发板裸机和带系统的emWin工程已经全部建立,链接如下: http://bbs.armfly.com/read.php?tid1830 SE…

python 栈【测试题】

文章目录1.删除最外层的括号信息要求答案2.棒球比赛信息示例答案3. 用栈实现队列要求说明:答案4.用队列模拟栈描述注意答案5.下一个更大的元素(未解)信息:示例:注意:答案:6.删除字符串中的所有相邻重复项信息示例&…

python进阶(第三章1) 字典

文章目录3.1 泛映射类型什么是可散列的数据类型(键的要求)字典的构造方法3.2 字典推导(dictcomp)3.3 常见的映射方法用setdefault处理找不到的键3.4 映射的弹性键查询3.4.1 defaultdict:处理找不到的键的一个选择注意:defaultdict与dict实例化…

python基础 list和tuple

文章目录一、list1、len()函数可以获得list元素的个数2、索引从0开始3、末尾追加 append(xx)4、也可以把元素插入到指定的位置,比如索引号为1的位置(insert)5、末尾删除pop() ,并且返回该值6、要删除指定位置的元素,用pop(i)方法,…

python基础 dict和set

文章目录dictset4.用集合为列表去重5.集合的增 add,update6.集合的删 discard,remove,pop,clear7 集合运算7.1 子集(<或者issubset()方法)7.2并集(|或者union()方法)7.3 交集(&或者intersection())7.4 差集(-或者difference()方法)7.5 对称集(^或者symmetric_difference…

python进阶(第三章2)字典和集合

文章目录3.8 集合论nee中的元素在haystack中出现的次数&#xff0c;可以在任何可迭代对象上3.8.1集合字面量3.8.2 集合推导3.8.3 集合操作3.9 dict和set的背后3.9.1 一个关于效率的实验3.9.2 字典中的散列表1.散列值和相等性2.散列表算法获取值&#xff1a;添加新的元素更新现有…

Android下实现GPS定位服务

1.申请Google API Key&#xff0c;参考前面文章 2.实现GPS的功能需要使用模拟器进行经纬度的模拟设置&#xff0c;请参考前一篇文章进行设置 3.创建一个Build Target为Google APIs的项目 4.修改Androidmanifest文件&#xff1a; view plain<uses-library android:name"…

DEDECMS全版本gotopage变量XSS ROOTKIT 0DAY

影响版本&#xff1a; DEDECMS全版本 漏洞描叙&#xff1a; DEDECMS后台登陆模板中的gotopage变量未效验传入数据&#xff0c;导致XSS漏洞。 \dede\templets\login.htm 65行左右 <input type"hidden" name"gotopage" value"<?php if(!empty($g…

Android开源库loopj的android-async-http的 JsonHttpResponseHandler 存在死循环GC_CONCURRENT

我现在用的是 AndroidAsyncHttp 1.4.4 版本&#xff0c;之前遇到一个很奇怪的问题&#xff0c; 当使用 JsonHttpResponseHandler 解析请求的页面出现服务器错误或其他情况返回的内容不是 JSON 字符串时不会调用自己复写实现的 onSuccess 或者 onFailure 方法&#xff0c;将会出…

进程状态转换(了解)

进程三个基本状态&#xff1a;就绪、阻塞、运行 这个比较简单&#xff0c;进程创建后进入就绪状态、然后若CPU空闲或能打断CPU正在执行的进程&#xff08;优先级低的&#xff09;&#xff0c;那么就绪状态转换成运行态&#xff0c;运行时&#xff0c;进程需要用到其他资源&…

2014阿里巴巴校园招聘笔试题 - 中南站

转载于:https://www.cnblogs.com/gotodsp/articles/3530329.html

Spring的IOC原理[通俗解释一下]

1. IoC理论的背景 我们都知道&#xff0c;在采用面向对象方法设计的软件系统中&#xff0c;它的底层实现都是由N个对象组成的&#xff0c;所有的对象通过彼此的合作&#xff0c;最终实现系统的业务逻辑。 图1&#xff1a;软件系统中耦合的对象 如果我们打开机械式手表的后盖&am…

以嵌入式系统设计师考试成绩,开始嵌入式博客之旅

http://www.rkb.gov.cn/jsj/cms/s_contents/download/s_dt201003110106.html 转载于:https://www.cnblogs.com/yueqian-scut/p/3952268.html

SSH框架配置及Maven使用

1.SSH框架配置 1.1. SSH框架介绍 1.2. SSH框架配置 所需资源下载&#xff1a; l jdk; 从Oracle官方网站&#xff1a;http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html下载jdk&#xff0c;win7是默认安装在C:\Program Files (x86)\Java\jdk1.6.0_25路径下…

Flask 从入门到熟悉(不敢称为精通)

文章目录2.1 Flask介绍及其安装2.2 Virtualenv3.1 一个最小的应用3.2 外部课件服务器3.3 调试模式4.1 路由介绍4.2 变量规则4.3 构建URL4.4 HTTP 方法4 总结5.1 静态文件5.2 渲染模板5.3 练习66.1 接收请求数据6.2 请求对象6.3 文件上传6.4 Cookies6 总结77.1 重定向和错误7.2 …

Ext JS 5 beta版发布

原文&#xff1a;Announcing Public Beta of Ext JS 5我们非常高兴的宣布&#xff0c;Sencha Ext JS 5 beta版本开始进行公测了。这个beta版本可以让你、我们Sencha社区来对我们的Ext JS 5的工作进度进行评测。对于所以Ext JS开发人员&#xff0c;这事一个很好的机会来协助完成…