对象引用 可变性 垃圾回收

 1 #变量标注 别名 相等性
 2 #变量是对象的标识,相当于对象的标签,而不是将对象装入变量的盒子,一个对象可以有多个标签。
 3 list1 = [1, 2, 3, [4, 5, 6]]
 4 print(id(list1[1]), id(2))
 5 #2、两个变量名相互赋值不会产生新对象,只是将赋值变量名对应的对象上多添加了一个别名,这个别名就是这个新的被赋值变量名
 6 list2 = list1 #list2 = list1这条语句并没有新的对象产生,只是将原有对象在添加了一个新标识List2而已
 7 list2.append(5)
 8 print("list1 %s; list2 %s"%(list1, list2))  #[1, 2, 3, 5]
 9 list2 = 5 #创建了新对象,将list2绑定到新对象上
10 print("new list2 is",list2)
11 
12 #一个对象可以有多个标签
13 var1 = "test"
14 var2 = "test"
15 print("var1 id is %s,var2 id is %s"%(id(var1), id(var2)))  #两个变量指向同一个对象,使用单例值对象绑定不同变量,这些变量指向的是相同的对象
16 var2 = "test1" #这一步实际是将var2这个变量贴到“test1”这个字符串上,而不再指向原来的“test”字符串。对于变量与单例值的比较建议使用is
17 print(var2 is "test1")  #true
18 list3 = ["wojiushiwo123%$#", 564, [123]]
19 list4 = ["wojiushiwo123%$#", 564, [123]]
20 print(list3 is list4) #false 对于非单例值对象绑定不同变量,这些变量指向的是不同的对象
21 
22 list5 = list(list1) #list5 = list1[:] 非单例值变量对象初始化另一变量,这种操作是浅复制;即内部包含的单例值对赋值与被初始化的
23 #两个变量来说已经是两个对象,但是对其内部包含的非单例值对象赋值前后两个变量内部对应索引或者键指向的是同一个变量
24 list5[3][0] = 5 #修改被赋值的list5内包含的非单例值对象,list1也一并被修改;这种赋值被称作浅复制。出现这种现象是因为非单例实例内部存储单例实例是存储的值本身
25 #如果内部存储非单例实例是存储的非单例实例的引用,再赋值操作时被赋值对象获取的仍是内层非单例值对象的引用,即指向同一个非单例值对象。
26 print(list1[3]) #[5, 5, 6]
27 
28 #python唯一支持的参数传递模式是共享传递,即传递参数的引用;实际传参时传递的是实参对象的引用副本,即会给实参再绑定一个新的引用
29 #对于不可变对象来说,由于对象本身不可修改性对于其标识的任何修改都会产生新的对象,add函数中由于对不可变变量使用+=进行操作,所以a指向新的对象7;原来的实参var3 var4指向的对象不收影响
30 def add(a, b):
31     a += b
32     return a
33 var3, var4 = 3, 4
34 print("var3 += var 4 is %d"%add(var3, var4)) #var3 += var 4 is 7
35 print("var3 is %d,var4 is %d"%(var3, var4)) #var3 is 3,var4 is 4
36 #当使用可变类型的实参传递给形参时,由于是对象引用,如果是对变量原地修改会体现在实参上,下面例子对list6进行了原地修改
37 list6 = [1, 2, 3]
38 list7 = [7, 8, 9]
39 add(list6, list7)
40 print("list6 is %s,list7 is %s"%(list6, list7)) #list6 is [1, 2, 3, 7, 8, 9],list7 is [7, 8, 9]
41 
42 #可变类型作为参数默认值如果处理不慎会有意想不到的错误,因为函数默认值值是定义函数时计算的,其相当于是函数对象的属性。因此如果默认值是可变对象并且我们修改了它,那么后续函数调用且使用默认值都会受影响
43 class bus:
44     def __init__(self, passengers=[]):
45         self.passengers = passengers
46     def pick(self, name):
47         self.passengers.append(name)
48     def drop(self, name):
49         self.passengers.remove(name)
50 bus1 = bus()
51 bus1.pick("zhangsan")
52 bus2 = bus()
53 print(bus2.passengers) #['zhangsan'] 神奇的事情发生了,bus2初始化时候使用默认值,其竟然包含了bus1中传入的值;这就是使用可变默认参数可能带来的隐患
54 
55 #防御可变参数,对上面bus类进行如下修改。注意:对于参数默认值不要在参数列表进行赋值,以免入坑
56 class bus:
57     def __init__(self, passengers=None):
58         if passengers is None:
59             self.passengers = [] #如果使用默认参数则每次都新建一个空列表
60         else:
61             self.passengers = passengers #如果需要修改对应实参可以使用这种变量别名,否则可以使用copy方法复制实参的副本
62     def pick(self, name):
63         self.passengers.append(name)
64     def drop(self, name):
65         self.passengers.remove(name)
66 
67 #del垃圾回收机制:del语句删除标识而不是对象本身,del命令可能导致变量被当做垃圾回收,仅限删除对象最后一个标识时。垃圾回收主要算法就是引用计数,当引用计数为0就会触发垃圾回收机制
68 
69 #python对不可变类型的特殊处理
70 #对于元组来说[:]和tuple(obj)竟然不创建副本,而是创建一个对象的引用,对str bytes frozenset也有相同的效果,这两种语法在列表对应是创建对象的副本
71 t1 = (1, 2, 3)
72 t2 = tuple(t1)
73 t3 = t1[:]
74 print(t1 is t2 is t3) #True
75 
76 #字符创和整数的驻留。Cpython会让简单的字符串和整数对象在所有标识之间共享一变节约内存,这种优化就称为驻留。  但是不会驻留所有字符串和整数,比较两个变量是否相等不能依赖驻留还是要使用==
77 t4 = (4, 5, 6)
78 t5 = (4, 5, 6)
79 print(t4 is t5) #false 两个元组分别初始化,即使值相同其指向的也是不同对象
80 str1 = "var3 += var 4 is"
81 str2 = "var3 += var 4 is"
82 print(str1 is str2) #True 两个字符串分别初始化,其指向的是相同的对象
83 
84 
85 """
86 总结:
87 每个python对象都有标识 类型和值,只有对象的值可能会变化,对象的标识只能绑定或者解绑,对象类型的变化一定会产生新的对象而不会原地修改
88 如果两个变量指代的不可变对象具有相同的值,实际上他们指代的对象副本还是别名基本没有什么关系,因为不可变对象的值是不能改变的
89 
90 变量保存的是引用,这一点对python变成有很多实际的影响
91 1、简单的赋值不能创建副本
92 2、对+= -+ *=这类操作来说如果左边是可变对象则对象进行原地修改,如果对象是不可变的则会产生新的对象
93 3、对现有变量重新赋值,不会修改之前绑定的变量,这叫做重新绑定。如果变量是之前对象的最后一个引用,那么之前的对象会被进行垃圾回收
94 4、函数参数以别名的形式传递,这意味着函数可能会通过修改参数传入的可变对象。
95 5、使用可变类型作为函数参数默认值是有危险的,如果就地修改了默认参数则会影响后续默认参数的使用
96 """

 

上一篇:25-Launcher类源码分析


下一篇:6.12---前提两个对象的成员必须一致,才能将有数据的对象将数据传给反射获取的对象conver(有数据对象,目标对象)