zoukankan      html  css  js  c++  java
  • 《流畅的Python》Object References, Mutability, and Recycling--第8章

    Object References, Mutability, and Recycling

    本章章节:

    • Variables Are Not Boxes
    • identity , Equality ,  Aliases
    • Copies are shallow by default
    • Function Parameters as references
    • del and Garbage Collection
    • Weak References
    • Tricks Python Plays with Immutable

    Variables

    翻译过来叫做变量,其实就是指针/标签。指向一个内存地址。而别名就是说同一个对象有多个标签。

    identity

    完全一样的两个对象,区别它们的是identity,Python使用id()函数得到一个对象的identity。

    ==符号是object.__eq__的语法糖。它会调用内存中的对象进行比较。

    is符号是object.__id__的语法糖,只会比较对象的id,自然运行速度快于==。

    Copies are shallow by default

    tuple模式是浅层复制。使用list()或者copy()复制的是tuple内的第一层对象。

    copy模块,深层复制

    可用于深层的复制。

    copy.copy(x)返回x层的浅层复制。

    Function Parameters as References

    call by sharing共享传参。这是Pyhon唯一支持的参数传递模式。Ruby等OO对象语言都是这种模式。

    一个函数可以改变传递给它,作为参数的可变对象(multable object)的值,但不能改变对象的identity。

    形式参数是别名:

    例子:

    def f(a, b):
        print(id(a), id(b))  #
        a += b
        print(id(a))
        return a
    
    x = 1
    print("id(x) is %s" % id(x))
    y = 2
    print("id(y) is %s" % id(y))
    f(x, y)
    id(x) is 4565326480
    id(y) is 4565326512
    4565326480 4565326512
    4565326544

    由此可知,x和a, y和b都是同一个对象的引用。所以说a, b只是别名。

    而a += b代码中的a, 的id改变了。这代表它引用的是另一个不可变对象(整数)。

    改变可变对象的值

    对上面的例子进行修改:

    def f(a, b):
        print(id(a), id(b))  #
        a += bprint("change variable a, but its id not change: {}".format(id(a)))
        return a
    
    x = [1, 2]
    print("id(x) is %s" % id(x))
    y = [2, 3]
    print("id(y) is %s" % id(y))
    print("return is {}".format(f(x, y)))
    print("x is {}".format(x))

     x是一个list,属于可变对象。

    id(x) is 4545995264
    id(y) is 4545996928
    4545995264 4545996928
    change variable a, but its id not change: 4545995264
    return is [1, 2, 2, 3]
    x is [1, 2, 2, 3]

    由此验证了一个函数可以改变传递给它,作为参数的可变对象(multable object)的值,但不能改变对象的identity。

    如果把x,y改成一个tuple,那么函数不能对x进行修改了。

    call by share原理,不要把可变对象作为默认的参数。

    这个原理体现了一些方便。但是:

    class HauntedBus:
        def __init__(self, passengers = []):
            self.passengers = passengers
    
        def pick(self, name):
            self.passengers.append(name)
    bus1 = HauntedBus()
    bus1.passengers.append("Tom")
    print("bus1 has {}".format(bus1.passengers))
    bus2 = HauntedBus()
    print("bus2 has {}".format(bus2.passengers))

    结果竟然是:

    bus1 has ['Tom']
    bus2 has ['Tom']

     bus2也有了一个乘客Tom!

     原因就是因为它们共享了一个参数passengers,它是一个list类型,是可变参数。由于共享,所以出现了两个对象数据混淆的问题。

     背后的过程是:passengers = []是在模块加载时,定义函数的过程中被计算。

    避免也简单,如果需要传入数据,在函数内进行复制,使用复制的数据。


    del and Garbage Collection

    Object不会被自行销毁,但是当认为是垃圾时,就会被当作垃圾回收。

    del命令删除的是name, 而不是对象本身。(name就是指向对象的指针)。

    当一个对象,没有指针来引用它,那么就被当成是垃圾,最后被销毁。

    垃圾回收的算法机制:reference counting

    每个对象有一个计数,记录对它的引用(指针)有多少个,当为0个时,对象就会被销毁。

    CPython会调用__del__方法,对象被销毁,释放内存。

    当然Python除了reference counting这种回收机制,还有更复杂的垃圾回收机制。

    例子:

    >>> import weakref
    >>> si  = {1, 2, 3}
    >>> s = si
    >>> def bye():
    ...     print("Gone with the wind...")
    >>> ender = weakref.finalize(si, bye)
    >>> ender.alive
    True
    >>> del si
    >>> ender.alive
    True
    >>> s = None
    Gone with the wind...
    >>> ender.alive
    False
    >>> ender
    <finalize object at 0x103892c70; dead>

    解释:

    1.模块weakref,用来创建对象的弱引用。术语referent表示使用“弱引用”引用“引用的对象”。⚠️弱引用不会被对象进行引用计数ref count。

    2, si和s共同引用一个dict。

    3.   当del si后,实际是删除si这个引用,而不是删除dict.

    4.   当s指向其他对象时,原对象的ref count变为0,因此启动垃圾回收机制,dict被删除,内存释放。

    5.   weakref.finalize(obj, func,)方法,返回一个可调用对象的终结器对象,用来管理对象的生存周期。 

    Weak References

    不会被对象记录的引用。主要用于缓存。被弱引用所指向的对象叫做referent。

    >>> class MyClass:
    ...     pass
    >>> o = MyClass()
    >>> r = weakref.ref(o)
    >>> r
    <weakref at 0x103a63b30; to 'MyClass' at 0x103a6f790>
    >>> o2 = r()
    >>> o2
    <__main__.MyClass object at 0x103a6f790>
    >>> o is o2
    True
    >>> del o, o2
    >>> print(r())
    None
    >>> r
    <weakref at 0x103a63b30; dead>

     使用ref(obj)创建一个弱引用r对象,  r()返回被引用的对象自身。

     当两个引用都被删除,引用对象就被垃圾回收机制所删除,这时r对象,内显示一个dead标记,表示r并没有引用的对象。

     

    需要注意的⚠️: 

    如果在平时使用时,不建议使用ref来创建弱引用对象,因为weakref.ref其实时一个底层接口,供高级用途使用的。比如finalize()或者weakref集合

    这个原因时因为在微观管理内存时,往往会得到意外的结果,比如不明显的隐式赋值会为对象创建新引用。

    《流畅的python》的实例8-17举了一个例子:

    控制台的_变量会自动绑定到结果不为None的表达式结果上,因此会对调用跟踪对象产生意料之外的引用。例子:

    >>> import weakref
    >>> s = {1}
    >>> wref = weakref.ref(s)
    >>> wref
    <weakref at 0x1022a7a40; to 'set' at 0x1022a5900>
    >>> wref()       #_变量自动的引用了{1}。
    {1}
    >>> s = "hello"
    >>> wref()      #虽然s不在指向{1},但此时_变量还指向{1}, 因此{1}仍然存在,所以使用wref()可以返回被引用的对象{1}
    {1}
    >>> wref() is None  #这个表达式返回False,代表此时{1}存在,但_变量重新指向了False,因此{1}的引进计数变为0,启动垃圾回收。
    False
    >>> wref() is None  #wref()返回None。
    True
    >>> wref
    <weakref at 0x1022a7a40; dead>

    小结:

    weakref其实就是配合垃圾回收来管理缓存的工具,上面讲解了弱引用的正常使用,它的原理,和背后的缺陷。并强调不要直接使用ref方法。

    WeakValueDictionary类的简介

    常用的管理缓存的工具类。

    它的实例是一个mutable mapping, 其中value是对某个对象的弱引用。当相关的某个对象被垃圾收集后,weakValueDictionary实例中对应的key会自动的被移除。

    除了WeakValueDictionary外还有WeakKeyDictionary,它的key是弱引用。

    弱引用的局限

    Python对象中,有些对象是不能被弱引用的:

    • int, tuple类的实例及相关子类的实例都不能弱引用。
    • dict和list类的实例不能弱引用,但子类可以。

    Tricks Python Plays with Immutables

    CPython的一些优化细节,对一般用户来说不重要。

    >>> s1= "hi,world"
    >>> s2= "hi,world"
    >>> s1 is s2
    False
    >>> s3 = "abc"
    >>> s4 = "abc"
    >>> s3 is s4
    True
    >>> id(s3)
    4362478384
    >>> id(s4)
    4362478384
    >>> id(s1)
    4363433648
    >>> id(s2)
    4363433264

    解释:

    上面s3之所以s4指向同一个对象,是因为"abc"是一个非常常用的字符串字面量,因此Python核心开发者对它做了优化,叫做interning, 扣押/驻留。

    ⚠️,使用==来判断,is是解释器内部常用的特征。

  • 相关阅读:
    Linux学习-网络管理
    Linux学习-文件权限
    Linux学习-用户管理常用命令
    python操作数据库(MySQL、redis、MD5加密函数)
    Jenkins待过的坑
    Robot framework+Jenkins
    jenkins + maven + SVN自动化集成部署
    接口测试学习笔记二
    接口测试学习笔记一
    【数据结构】为什么要使用一致性哈希算法?
  • 原文地址:https://www.cnblogs.com/chentianwei/p/12042188.html
Copyright © 2011-2022 走看看