流畅的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
 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掉,避免浪费内存。

你可能感兴趣的:(流畅的python,Fluent Python 第八章笔记)