流畅的python,Fluent Python 第八章笔记
对象引用,可变性,垃圾回收、
8.1 变量不是盒子
这一章相对来说概念比较多,我前期已经粗粗看了一遍,挑选我觉的经典的记录。
a = [1 ,2, 3]
按照说中书法,正确的理解是把变量(变量名)a分配给了对象([1,2,3])
毕竟对象在赋值之前已经创建。
为了理解Python中的赋值语句,应该始终先读右边。对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像对对象贴上标注。
8.2标识、相等性和别名
a = 1
b = a
a与b是别名,因为他们同时绑定了一个对象
is判断两个变量是否id相等。
ID一定是唯一的数值标注,而且在对象的生命周期中绝不会变。
8.3默认做浅复制
一般的复制(拷贝)都是浅拷贝,浅拷贝就是复制了最外层容器,副本内的元素是容器中元素的引用,好处就是节省内存。
l = [1, [2, 3, ], 'ok'] l1 = list(l) l2 = l[:] l3 = l.copy()
上面三种都是浅拷贝。就复制了最外层的,除非里面的元素都是不可变元素,要不然里面的元素副本都是引用,还是会带来问题。
为任意对象做深拷贝:
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) if __name__ == '__main__': bus1 = Bus(['1', '2', '3']) bus2 = copy.copy(bus1) bus3 = copy.deepcopy(bus1) # 用了copy内部的属性引用还是一样的 print(id(bus1.passengers),id(bus2.passengers),id(bus3.passengers)) bus1.drop('2') print(bus1.passengers, bus2.passengers, bus3.passengers, sep='\n')
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第八章/t8_8.py 4563857312 4563857312 4563740464 ['1', '3'] ['1', '3'] ['1', '2', '3'] Process finished with exit code 0
copy拷贝的对象,只拷贝最表层,里面的属性引用地址都是一样的。
8.4 函数的参与作为引用时
Python唯一支持的参数传递模式时共享传参。
共享传参指函数的各个形式参数获得实参中各个引用的副本,直接的说,函数内部的形参时实参的别名。
所以当传入的参数为可变类型时,内部的形参改变参数,外部的实参也会发生变换。
8.4.1不要使用可变类型作为参数的默认值
# t8_12 class HauntedBus: def __init__(self, passengers=[]): self.passengers = passengers def pick(self, name): self.passengers.append(name) def drop(self, name): self.passengers.remove(name)
from t8_12 import HauntedBus In [78]: bus2 = HauntedBus() In [79]: bus2.pick('sidian') In [80]: bus3 = HauntedBus() In [81]: bus3.passengers Out[81]: ['sidian'] In [82]: bus2.passengers is bus3.passengers Out[82]: True In [84]: bus2.__init__.__defaults__ Out[84]: (['sidian'],) In [85]: bus3.pick('gray') In [86]: bus2.__init__.__defaults__ Out[86]: (['sidian', 'gray'],) In [87]: HauntedBus.__init__.__defaults__ Out[87]: (['sidian', 'gray'],) In [88]: bus2.__init__.__defaults__ Out[88]: (['sidian', 'gray'],) In [89]: import dis In [90]: dis.dis(HauntedBus) Disassembly of __init__: 4 0 LOAD_FAST 1 (passengers) 2 LOAD_FAST 0 (self) 4 STORE_ATTR 0 (passengers) 6 LOAD_CONST 0 (None) 8 RETURN_VALUE Disassembly of drop: 10 0 LOAD_FAST 0 (self) 2 LOAD_ATTR 0 (passengers) 4 LOAD_METHOD 1 (remove) 6 LOAD_FAST 1 (name) 8 CALL_METHOD 1 10 POP_TOP 12 LOAD_CONST 0 (None) 14 RETURN_VALUE Disassembly of pick: 7 0 LOAD_FAST 0 (self) 2 LOAD_ATTR 0 (passengers) 4 LOAD_METHOD 1 (append) 6 LOAD_FAST 1 (name) 8 CALL_METHOD 1 10 POP_TOP 12 LOAD_CONST 0 (None) 14 RETURN_VALUE
为什么会这样因为定义函数的时候(通常在加载的时候),默认值变成了函数对象的属性。
所以在定义函数的时候,默认指为可变参数,也会发送同样的问题。
8.5 del和垃圾回收
del删除的是名称是变量,但并不是那个对象。
Python的垃圾清楚,主要用的是引用技术,另外还有分代收集,标记清除。
上一段弱引用的事例,弱引用可以帮你获得对象,但不会增加对象的引用数量。
Python 3.7.4 (default, Jul 9 2019, 18:13:23) Type 'copyright', 'credits' or 'license' for more information IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help. PyDev console: using IPython 7.7.0 Python 3.7.4 (default, Jul 9 2019, 18:13:23) [Clang 10.0.1 (clang-1001.0.46.4)] on darwin import weakref, sys s1 = {1,2,3} s2 = s1 def bye(): print('Gone with the wind...') ender = weakref.finalize(s1 ,bye) sys.getrefcount(s1) Out[7]: 3 del s2 sys.getrefcount(s1) Out[9]: 2 ender.alive Out[10]: True del s1 Gone with the wind... ender.alive Out[12]: False
经过本人多次调试,在shell中执行,书中的weakref.ref实例获取的对象,该对象一旦执行(),内部的对象标签直接上升很多。但在py文件里面可以执行。
先上在py文件正常执行的代码:
import weakref import sys a_set = {0, 1} func = lambda x: print(repr(x), 'is deleting') wref = weakref.ref(a_set, func) print(sys.getrefcount(a_set)) print(wref()) print(sys.getrefcount(a_set)) a_set = {0 ,1} # 重新复制,相当于删除了老的便签变量 print(wref())
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第八章/t8_17.py 2 {0, 1} 2 <weakref at 0x10d0976b0; dead> is deleting None Process finished with exit code 0
然后上在python 控制台写的代码:
/usr/local/bin/python3.7 /Applications/PyCharm.app/Contents/helpers/pydev/pydevconsole.py --mode=client --port=54889 import sys; print('Python %s on %s' % (sys.version, sys.platform)) sys.path.extend(['/Users/shijianzhong/study', '/Users/shijianzhong/study/Fluent_Python/第六章']) Python 3.7.4 (default, Jul 9 2019, 18:13:23) Type 'copyright', 'credits' or 'license' for more information IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help. PyDev console: using IPython 7.7.0 Python 3.7.4 (default, Jul 9 2019, 18:13:23) [Clang 10.0.1 (clang-1001.0.46.4)] on darwin import weakref import sys a_set = {0, 1} func = lambda x: print(repr(x), 'is deleting') wref = weakref.ref(a_set, func) print(sys.getrefcount(a_set)) 2 wref() Out[3]: {0, 1} sys.getrefcount(a_set) Out[4]: 9
对象的便签一下子变成了9,那就没有后面的事情了。
后面weakref提供
In [225]: weakref.WeakKeyDictionary
Out[225]: weakref.WeakKeyDictionary
In [226]: weakref.WeakValueDictionary
Out[226]: weakref.WeakValueDictionary
In [227]: weakref.WeakSet
Out[227]: _weakrefset.WeakSet
三个模块,我自己用了一下WeakSet的模块,还是蛮有意思的,在一个WeakSet里面添加一个类属性,把所有还活着的对象在这个WeakSet里面,可以方便的查看活着的实例。
from weakref import WeakSet class My_Demo: instances = WeakSet() def __init__(self,name): self.name = name My_Demo.instances.add(self) # 初始化的时候,把实例放入WeakSet()里面 my1 = My_Demo('my1') my2 = My_Demo('my2') del my2 # 删掉一个实例 my3 = My_Demo('my3') print(My_Demo.instances) print(my1.instances) # 通过实例当然也可以调用类属性。
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第八章/t8_19_1.py <_weakrefset.WeakSet object at 0x107f1e3d0> <_weakrefset.WeakSet object at 0x107f1e3d0> Process finished with exit code 0
本章小结(个人就摘录了两点):
简单的赋值不创建副本
对+= ,*=所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建对象,如果是可变对象,会就地修改。
本来的理解,书中我把变量,变量名,别名,可以理解为同一个事物,都在=的左边。
=的右边是对象,是先与=的左边创建的。
=左边的是在对象上面贴标签,也可以认为指向了对象,或者像是对象的快捷方式。
我们不能直接删除对象,只能删除=号左边的,等把对象上面的所有便签都撕掉了,该对象也就升天了。
所以以后在写程序时,一些不用的对象可以del掉,避免浪费内存。