一、核心要义
本章主要讨论对象和对象名称之间的区别。名称不是对象,而是单独的东西。
二、代码示例
1、标识、相等性和别名
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 10:58
# @Author : Maple
# @File : 01-标识,相等性和别名.py
# @Software: PyCharmp1 = {'name':'maple','gender':'male','age':32}
p2 = p1
p3 ={'name':'maple','gender':'male','age':32}if __name__ == '__main__':# 1.p1所指向对象的标识(CPython中指对象的内存地址)print(id(p1)) # 1404688094144print('*******************************')# 2.p1和p2指向同一个对象,p2也被称作p1的别名print(id(p2))# 1404688094144print(id(p1) == id(p2)) # Trueprint('*******************************')# 3.p3虽然和p1的值完全相同,但其实指向不同的对象# 值相等print(p1 == p3) # True# 但其实指向不同的对象print(id(p3)) # 1404688094208print(id(p3) == id(p1)) # False# 实际开发很少使用id方式判断是否 同一个对象,而是使用isprint(p3 is p1) # False
2、元组的相对不可变性
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 11:05
# @Author : Maple
# @File : 02-元组的相对不可变性.py
# @Software: PyCharm"""
元组的不可变性是指tuple数据结构的物理内容(即保存的引用)不可变,但如果引用 的某个可变对象本身发生了变化
,元组的值其实也会发生变化
"""# 元组中的第三个元素[30,40]是可变对象-列表
t1 = (1,2,[30,40])
t2 = (1,2,[30,40])if __name__ == '__main__':# 1. 最开始,t1和t2的值是相等的print(t1 == t2) # True# 2.修改t1中第三个元素的值## 修改前列表的标识print(id(t1[-1])) # 2250527148480t1[-1].append(99)print(t1[-1]) # [30, 40, 99]## 修改后列表的标识(与修改前相同,也就是引用本身 并不会发生变化)print(id(t1[-1])) # 2250527148480## 但是引用 所对应的值很显然已经发生了变化print(t1[-1] == t2[-1]) # False
3、浅拷贝
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 11:21
# @Author : Maple
# @File : 03-浅拷贝.py
# @Software: PyCharm"""
浅拷贝:副本中的元素是源容器中元素的引用
"""l1 = [3,[66,55,44],(7,8,9)]
# l2是l1的浅拷贝
l2 = list(l1)if __name__ == '__main__':#0. l1和l2是不同的对象print(l1 is l2 ) # False# 但是l1和l2中各个元素的引用都是指向同一个对象for i in range(len(l1)):print(l1[i] is l2[i]) # True,True,True# 1. l1中追加100,对l2没有影响l1.append(100)print('l1:',l1,';l2:',l2) # l1: [3, [66, 55, 44], (7, 8, 9), 100] ;l2: [3, [66, 55, 44], (7, 8, 9)]#2. l1中第一个元素修改为30,对l2没有影响,因为对数值进行修改,会直接生成一个新的对象,l1[0] = 30print('l1:', l1, ';l2:', l2) # l1: [30, [66, 55, 44], (7, 8, 9), 100] ;l2: [3, [66, 55, 44], (7, 8, 9)]#3. l1第二个元素移除55,l2会同步移除l1[1].remove(55)print('l1:', l1, ';l2:', l2) # l1: [30, [66, 44], (7, 8, 9), 100] ;l2: [3, [66, 44], (7, 8, 9)]#4. l2中第二个元素增加[33,22],因为list是可变对象,+=操作会就地改变对象本身(可参考第二章`拼接和增量操作`部分)#因此l1中的第二个也会同步发生变化l2[1] += [33,22]print('l1:', l1, ';l2:', l2) #l1: [30, [66, 44, 33, 22], (7, 8, 9), 100] ;l2: [3, [66, 44, 33, 22], (7, 8, 9)]#5. l2中第三个元素增加(10,11),因为元组是不可变对象,+=操作会生成一个新的对象(可参考第二章`拼接和增量操作`部分)#因此不会影响l1中的对应值l2[2] +=(10,11)print('l1:', l1, ';l2:', l2) #l1: [30, [66, 44, 33, 22], (7, 8, 9), 100] ;l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
补充:l1和l2的内存图:
(1)最初状态
(2)最终状态
4、深拷贝
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 11:44
# @Author : Maple
# @File : 04-深拷贝.py
# @Software: PyCharmimport copyclass 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)if __name__ == '__main__':bus1 = Bus(['Maple','Alice','Carry'])# bus1的浅拷贝bus2 = copy.copy(bus1)#bus1的深拷贝bus3 = copy.deepcopy(bus1)# 首先bus1,bus2和bus3是不同的对象print(id(bus1),',',id(bus2),',',id(bus3)) # 1973781178160 , 1973781642592 , 1973781754688# 但是由于bus2是bus1的浅拷贝,所以bus1.passengers和bus2.passengers指向同一个对象,而bus3.passengers则指向另外一个对象print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)) # 1973781759040 1973781759040 1973781758912# 从bus1.passengers中移除Maple,bus1.passengers也会发生相应变化bus1.drop('Maple')print(bus2.passengers) # ['Alice', 'Carry']# bus3.passengers 不受影响print(bus3.passengers) # ['Maple', 'Alice', 'Carry']
5、函数参数引用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 13:08
# @Author : Maple
# @File : 05-函数参数引用.py
# @Software: PyCharml1 = [3,[66,55,44],(7,8,9)]def f(a):"""函数形参是实参的别名,即两者指向同一个对象"""return id(a)def f2(a,b):# 形参a是实参的一个副本print('局部变量a运算前:',id(a))a +=bprint('局部变量a运算后:',id(a))return aif __name__ == '__main__':# 1. 形参和实参指向同一个对象print(id(l1)) #1596413019840print(f(l1)) #1596413019840print('************************')# 2. 数值类型测试a1 = 1b1 = 2print('全局变量a:',id(a1)) # 140724639640480""" 局部变量a运算前: 140724639640480局部变量a运算后: 140724639640544形参a返回值: 3"""print('形参a返回值:',f2(a1,b1)) # 3: 形参a指向一个新的对象(对于数值类型,+运算会生成一个新的对象)print(a1) # 1:实参a1并不会发生变化print('*******************************')# 3.列表类型测试a2 = [1,2,3]b2 = [4]print('全局变量a:', id(a2)) # 2096208446016""" 局部变量a运算前: 2096208446016局部变量a运算后: 2096208446016形参a返回值: [1, 2, 3, 4]"""print('形参a返回值:', f2(a2, b2)) # 3: 形参a就地修改原对象(对于列表类型,+运算会就地修改对象本身)print(a2) # [1, 2, 3, 4]:实参a2也会同步发生变化print('*******************************')# 4.元组测试a3 = (1, 2, 3)b3 = (4,)print('全局变量a:', id(a3)) # 3147866564096""" 局部变量a运算前: 3147866564096局部变量a运算后: 3147866469536形参a返回值: (1, 2, 3, 4)"""print('形参a返回值:', f2(a3, b3)) # 3: 形参a指向一个新的对象(对于元组类型,+运算会生成一个新的对象)print(a3) # (1, 2, 3):实参a3并不会发生变化
6、使用不可变对象作为函数参数默认值
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 14:35
# @Author : Maple
# @File : 06-使用不可变对象作为函数参数默认值.py
# @Software: PyCharm"""
函数参数的默认值应该尽量选择不可变类型,如果使用可变类型(比如list)可能会存在一些隐患
"""class Bus:# passengers默认值使用可变类型的listdef __init__(self,passengers = []):# self.passengers 是passengers的一个别名,即两者是同一个对象# 同时passengers又是空列表的一个别名# 而空列表作为默认值,会在函数定义时加载的,也就是默认值会变成函数对象的一个属性self.passengers = passengersdef pick(self,name):self.passengers.append(name)def drop(self,name):self.passengers.remove(name)if __name__ == '__main__':# 查看对象的默认属性:此时还是默认属性值还不包括Mapleprint(Bus.__init__.__defaults__)bus1 = Bus()bus1.pick('Maple')# 再次查看对象的默认属性,因为函数又一次加载了,同时在bus1.pick中已经添加了一个元素,所以默认属性值中会有Mapleprint(Bus.__init__.__defaults__)bus2 = Bus()# bus2的passengers竟然有值,明明初始化是空,原因分析# 1. bus1创建时,self.passengers指向 空列表对象,同时往该对象添加元素'Maple'# 2. bus2创建时,self.passengers指向 同一个`空列表`对象,只不过此时对象(默认属性)里已经有了值:Mapleprint(bus2.passengers) # ['Maple']
7、防御可变参数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 14:53
# @Author : Maple
# @File : 07-防御可变参数.py
# @Software: PyCharmclass TwilightBus:"""篮球队成员从 bus下车后,竟然同时从篮球队消失,不符合常理"""def __init__(self,basketball_team):if basketball_team is None:self.basketball_team = []else:self.basketball_team = basketball_teamdef pick(self,member):self.basketball_team.append(member)def drop(self,member):self.basketball_team.remove(member)class TwilightBus2:def __init__(self,basketball_team):if basketball_team is None:self.basketball_team = []else:# 创建basketball_team的副本(浅拷贝) 赋值给self.basketball_teamself.basketball_team = list(basketball_team)def pick(self,member):self.basketball_team.append(member)def drop(self,member):self.basketball_team.remove(member)if __name__ == '__main__':#1. TwilightBus测试basketball_team = ['Maple','Kelly','Jack']bus = TwilightBus(basketball_team)bus.drop('Maple')# Maple从bus下车后,连带从basketball_team中消息了,为何?# 1. self.basketball_team和 basketball_teams 指向同一个对象(互为别名),# 因此针对 self.basketball_team的操作,会影响basketball_teams的值# 解决方式是创建 basketball_teams的副本,让针对self.basketball_team的操作不会影响到basketball_teamsprint(basketball_team) # ['Kelly', 'Jack']#2. TwilightBus2测试basketball_team2 = ['Maple', 'Kelly', 'Jack']bus2 = TwilightBus2(basketball_team2)bus2.drop('Maple')print(basketball_team2) # ['Maple', 'Kelly', 'Jack']
八、del和弱引用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 15:06
# @Author : Maple
# @File : 08-del和弱引用.py
# @Software: PyCharm"""
1.del删除变量名称而已,并不会直接删除对象
2.当对象的引用计数变为0时,对象立即就被销毁
"""import weakrefs1 = {1,2,3}
s2 = s1
def bye():print('随风而逝....')a_set = {0,1}if __name__ == '__main__':ender = weakref.finalize(s1,bye)print(ender.alive) # True# 虽然删除了s1,但{1,2,3}仍然还剩下s2这个引用del s1# 所以ender仍然aliveprint(ender.alive) # True# s2指向一个新的对象,{1,2,3}的引用计数变为0s2 = 'spam'# 因此ender.alive变为Falseprint(ender.alive) # 随风而逝..../False# 遗留一个问题,按理说函数形参 也会指向{1,2,3},所以当s1和s2被删除后,{1,2,3}应该还剩一个引用# 但因为 形参是对{1,2,3}的弱引用,并不会占用引用计数"""控制台会话:Python控制台会自动把_变量绑定到结果不为None的表达式结果上>>>import weakref>>>a_set = {0,1}# wref即是对a_set的弱引用>>>wref = weakref.ref(a_set)# 返回弱引用对象的值:即{0,1},此时系统会自动分配一个变量_指向{0,1}>>>wref(){0, 1}# a_set重新指向一个新的对象(注意此时仍然有一个变量_ 指向{0,1},所以{0,1}并不会被销毁)>>>a_set = {1}# 仍然是{0,1}>>>wref(){0, 1}# 执行完,系统自动分配的变量指向False,因此没有变量指向{0,1},该对象会被销毁,之后wref()也就为None了>>>wref() is NoneFalse>>>wref() is NoneTrue"""