流畅的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掉,避免浪费内存。
浙公网安备 33010602011771号