本章主要讨论对象和对象名称之间的区别。名称不是对象,而是单独的东西。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 10:58
# @Author : Maple
# @File : 01-标识,相等性和别名.py
# @Software: PyCharm
p1 = {'name':'maple','gender':'male','age':32}
p2 = p1
p3 ={'name':'maple','gender':'male','age':32}
if __name__ == '__main__':
# 1.p1所指向对象的标识(CPython中指对象的内存地址)
print(id(p1)) # 1404688094144
print('*******************************')
# 2.p1和p2指向同一个对象,p2也被称作p1的别名
print(id(p2))# 1404688094144
print(id(p1) == id(p2)) # True
print('*******************************')
# 3.p3虽然和p1的值完全相同,但其实指向不同的对象
# 值相等
print(p1 == p3) # True
# 但其实指向不同的对象
print(id(p3)) # 1404688094208
print(id(p3) == id(p1)) # False
# 实际开发很少使用id方式判断是否 同一个对象,而是使用is
print(p3 is p1) # False
#!/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])) # 2250527148480
t1[-1].append(99)
print(t1[-1]) # [30, 40, 99]
## 修改后列表的标识(与修改前相同,也就是引用本身 并不会发生变化)
print(id(t1[-1])) # 2250527148480
## 但是引用 所对应的值很显然已经发生了变化
print(t1[-1] == t2[-1]) # False
#!/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] = 30
print('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)最终状态
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 11:44
# @Author : Maple
# @File : 04-深拷贝.py
# @Software: PyCharm
import copy
class Bus:
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)
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']
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 13:08
# @Author : Maple
# @File : 05-函数参数引用.py
# @Software: PyCharm
l1 = [3,[66,55,44],(7,8,9)]
def f(a):
"""函数形参是实参的别名,即两者指向同一个对象"""
return id(a)
def f2(a,b):
# 形参a是实参的一个副本
print('局部变量a运算前:',id(a))
a +=b
print('局部变量a运算后:',id(a))
return a
if __name__ == '__main__':
# 1. 形参和实参指向同一个对象
print(id(l1)) #1596413019840
print(f(l1)) #1596413019840
print('************************')
# 2. 数值类型测试
a1 = 1
b1 = 2
print('全局变量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并不会发生变化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 14:35
# @Author : Maple
# @File : 06-使用不可变对象作为函数参数默认值.py
# @Software: PyCharm
"""
函数参数的默认值应该尽量选择不可变类型,如果使用可变类型(比如list)可能会存在一些隐患
"""
class Bus:
# passengers默认值使用可变类型的list
def __init__(self,passengers = []):
# self.passengers 是passengers的一个别名,即两者是同一个对象
# 同时passengers又是空列表的一个别名
# 而空列表作为默认值,会在函数定义时加载的,也就是默认值会变成函数对象的一个属性
self.passengers = passengers
def pick(self,name):
self.passengers.append(name)
def drop(self,name):
self.passengers.remove(name)
if __name__ == '__main__':
# 查看对象的默认属性:此时还是默认属性值还不包括Maple
print(Bus.__init__.__defaults__)
bus1 = Bus()
bus1.pick('Maple')
# 再次查看对象的默认属性,因为函数又一次加载了,同时在bus1.pick中已经添加了一个元素,所以默认属性值中会有Maple
print(Bus.__init__.__defaults__)
bus2 = Bus()
# bus2的passengers竟然有值,明明初始化是空,原因分析
# 1. bus1创建时,self.passengers指向 空列表对象,同时往该对象添加元素'Maple'
# 2. bus2创建时,self.passengers指向 同一个`空列表`对象,只不过此时对象(默认属性)里已经有了值:Maple
print(bus2.passengers) # ['Maple']
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/2/8 14:53
# @Author : Maple
# @File : 07-防御可变参数.py
# @Software: PyCharm
class TwilightBus:
"""篮球队成员从 bus下车后,竟然同时从篮球队消失,不符合常理"""
def __init__(self,basketball_team):
if basketball_team is None:
self.basketball_team = []
else:
self.basketball_team = basketball_team
def 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_team
self.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_teams
print(basketball_team) # ['Kelly', 'Jack']
#2. TwilightBus2测试
basketball_team2 = ['Maple', 'Kelly', 'Jack']
bus2 = TwilightBus2(basketball_team2)
bus2.drop('Maple')
print(basketball_team2) # ['Maple', 'Kelly', 'Jack']
#!/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 weakref
s1 = {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仍然alive
print(ender.alive) # True
# s2指向一个新的对象,{1,2,3}的引用计数变为0
s2 = 'spam'
# 因此ender.alive变为False
print(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 None
False
>>>wref() is None
True
"""