不愿透露姓名的高杨

不愿透露姓名的高杨

  • 简单总结,当我们执行a=3的时候,实际做了三件事:

    这里提到了一个概念,引用。 引用其实就是一种关系,是通过内存中的指针所实现的。

    好嘞,这里又出现了一个新的概念,指针。 指针这个东西,简单来说可以理解为内存地址的一个指向。就是对初学者不好解释(主要是我懒得解释,就是属于那种懂的不需要讲,不懂的一时半会讲了也是不懂,但是随着学习的深入,慢慢就理解了的东西。。。)

    变量的类型

    首先,python是一个强类型语言,这是毫无疑问的。 但是python不需要显式的声明变量类型。 这是因为python的类型是记录在对象实例中的。

    在前面我们讲到过,python中的对象会包含两个重要的头部信息:

    因为对象的这个机制,python中的变量声明的时候,就不需要再指定类型了。 也就是说变量名与变量类型是无关的。

    a=1
    a='spam'
    a=1.123

    而且如上所示,同一个变量名可以赋值给不同类型的对象实例。

    共享引用

    这里提出一个问题,如下代码:

    In [6]: a=3
    In [7]: b=a
    In [8]: a='spam'

    那么在经过这一系列操作之后,a和b的值分别是啥?

    In [9]: a
    Out[9]: 'spam'

    In [10]: b
    Out[10]: 3

    首先我们来看,在执行a=3b=a之后,发生了什么

    a=3根据之前的介绍,比较好理解了。b=a实际上变量名b只是复制了a的引用,然后b也引用到了对象实例3上。那在之后这一句a='spam'又发生了什么?

    这个图就说的很清楚了,在我们执行了a='spam'之后,a被指向了另外一个对象。

    搞清楚了这个之后,我们再来看下一个例子:

    a=3
    b=a
    a=a+3

    这个前两句就不需要解释了,第三句a=a+3 其实一眼就可以看出来,此时a是6。这个就涉及到前面说的,当a出现在表达式中的时候,它就会“变成”它所引用的对象实例。a=a+3也就是会变成3+3 计算后得出新的对象实例6,然后变量a引用到6这个对象上。

    在原位置修改

    关于共享引用,这里看一个特殊的例子:

    In [16]: L1=[1,2,3]

    In [17]: L2=L1

    In [18]: L1[0]=1111

    In [19]: L1
    Out[19]: [111123]

    In [20]: L2
    Out[20]: [111123]

    按照之前的剧本,L2和L1都是指向列表[1,2,3]这个对象的,那为什么在我们修改L1[0] 这个元素之后,为什么L2也跟着发生变化了呢?

    我自己画了图,从这个图可以看出来,实际上对于L1和L2的共享引用来看,并没有违反我们上面说的共享引用的原则。只是对于序列中元素的修改,L1[0]会在原位置覆盖列表对象中的某部分值。

    那么问题来了如果在修改L1[0]之后,并不想L2的值受到影响,那该怎么办?

    简单

    把列表原原本本的复制一份就好了。 复制的办法有三种:

    第一种针对列表而言,可以直接创建一个完整的切片,本质上是一种浅拷贝。

    In [32]: L1=[[1,2,3],4,5,6]

    In [33]: L2=L1[:]

    In [34]: L2
    Out[34]: [[123], 456]

    In [37]: L1[2]='aaa'

    In [38]: L2
    Out[38]: [[111123], 456]

    In [39]: L1
    Out[39]: [[111123], 4'aaa'6]

    第二种,浅拷贝,如下面这个例子中的D1.copy()

    In [26]: D1={a:[1,2,3],b:3}

    In [27]: import copy

    In [28]: D2=D1.copy()

    In [29]: D2
    Out[29]: {6: [123], 33}

    In [30]: D1[a][0]=1111

    In [31]: D2
    Out[31]: {6: [111123], 33}

    第三种,深拷贝,如下D2=copy.deepcopy(D1)

    In [41]: import copy
        
    In [45]: D1={'A':[1,2,3],'B':'spam'}

    In [46]: D1
    Out[46]: {'A': [123], 'B''spam'}

    In [47]: D2=copy.deepcopy(D1)

    In [48]: D2
    Out[48]: {'A': [123], 'B''spam'}

    In [49]: D1['A'][0]=1111

    In [50]: D1
    Out[50]: {'A': [111123], 'B''spam'}

    In [51]: D2
    Out[51]: {'A': [123], 'B''spam'}

    我相信,看到这里,对于深拷贝和浅拷贝有些读者已经明白了,但是有些读者还是迷糊的。 这里简单说一下,

    更详细的内容见: Python 直接赋值、浅拷贝和深度拷贝解析

    关于相等

    先看一个例子

    In [59]: L1=[1,2,3]

    In [60]: L2=L1

    In [61]: L1==L2
    Out[61]: True

    In [62]: L1 is L2
    Out[62]: True
    In [66]: L1=[1,2,3]

    In [67]: L2=[1,2,3]

    In [68]: L1==L2
    Out[68]: True

    In [69]: L1 is L2
    Out[69]: False

    从上面这个例子就可以看出来,==比较的是值,is 实际比较的是实现引用的指针。

    对象的垃圾收集和弱引用

    垃圾回收机制也是一件很复杂的事情,但是python编译器可以自己去处理这玩意儿。 所以在初级阶段,我们不需要过多关注这玩意儿。 知道有这么个东西就够了。

    这里简单的介绍下,python中的垃圾回收就是我们所谓的GC,靠的是对象的引用计数器。引用计数器为0的时候,这个对象实例就会被释放。对象的引用计数器可以通过sys.getrefcount(istance)来查看。

    In [70]: import sys

    In [72]: sys.getrefcount(1)
    Out[72]: 2719

    引用计数器的引入可以很好的跟踪对象的使用情况,但是在某些情况下,也可能会带来问题。 比如循环引用的问题。

    如下代码:

    In [73]: L =[1,2,3]

    In [74]: L.append(L)

    当然,正常人肯定不会写出这种智障代码,但是在一些复杂的数据结构中,子对象互相引用,就可能会造成死锁。比如:

    In [1]: class Node:
       ...:   def __init__(self):
       ...:     self.parent=None
       ...:     self.child=None
       ...:   def add_child(self,child):
       ...:     self.child=child
       ...:     child.parent=self
       ...:   def __del__(self):
       ...:     print('deleted')
       ...:

    这里我们定义了一个简单的类。这时,如果我们创建一个节点,然后删除它,可以看到,对象被回收,并且准确的打印出了deleted。

    In [2]: a=Node()

    In [3]: del a
    deleted

    那么,像下面这个例子,在删除a节点之后,貌似没有触发垃圾回收,只有手动的gc之后,这两个对象实例才被删除。

    在删除a之后,没有触发垃圾回收,是因为它俩互相引用,实例的引用计数器并没有置0 。

    那在手动gc之后,由于python的gc会检测这种循环引用,并删除它。

    In [4]: a=Node()

    In [5]: a.add_child(Node())

    In [6]: del a

    In [7]: import gc

    In [8]: gc.collect()
    deleted
    deleted
    Out[8]: 356

    那么如果使用弱引用的话,效果就不一样了

    In [9]: import weakref
       ...:
       ...: class Node:
       ...:   def __init__(self):
       ...:     self.parent=None
       ...:     self.child=None
       ...:   def add_child(self,child):
       ...:     self.child=child
       ...:     child.parent=weakref.ref(self)
       ...:   def __del__(self):
       ...:     print('deleted')
       ...:

    In [10]: a=Node()

    In [11]: a.add_child(Node())

    In [12]: del a
    deleted
    deleted

    所以这里就可以看出来,所谓弱引用,其实并没有增加对象的引用计数器,即使弱引用存在,垃圾回收器也会当做没看见。

    弱引用一般可以拿来做缓存使用,对象存在时可用,对象不存在的时候返回None。这正符合缓存有则使用,无则重新获取的性质。

    12-15 16:59