流畅的python读书笔记-第八章-对象引用、可变性和垃圾回收

对象不是个盒子

clipboard.png

class Gizmo:
    def __init__(self):
        print('Gizmo id: %d' % id(self))

x = Gizmo()
print(x)

y = Gizmo() * 10
print(y)

print(dir())
❶ 输出的 Gizmo id: ... 是建立 Gizmo 实例的反作用。
❷ 在乘法运算中使用 Gizmo 实例会抛出异常。
❸ 这里代表,在尝试求积以前其实会建立一个新的 Gizmo 实例。
❹ 可是,确定不会建立变量 y,由于在对赋值语句的右边进行求值时抛出了异常。
 为了理解 Python 中的赋值语句,应该始终先读右边。对象在右边建立或获取,在此以后左边的变量才会绑定到对象上,

标识、相等性和别名

longe = {'name': 'longe', 'born': 1993}
liang = longe
print(liang is longe)

print(id(liang), id(longe))

longe['balance'] = 950

print(liang)

## 冒充的longe信息

other  = {'name': 'longe', 'born': 1993, 'balance': 950}
print(other)

print(other is longe)

❶ liang 是 longe 的别名。
❷ is 运算符和 id 函数确认了这一点。
❸ 向 liang 中添加一个元素至关于向 longe 中添加一个元素。html

在那段代码中,liang 和 longe 是别名,即两个变量绑定同一个对象。
而 other 不是 longe 的别名,由于两者绑定的是不一样的对象。python

other 和longe 绑定的对象具备相同的值(== 比较的就是值),可是它们的标识不一样。
  1. 每一个变量都有标识、类型和值。对象一旦建立,它的标识毫不会变;
  2. 你能够把标识理解为对象在内存中的地址。
  3. is 运算符比较两个对象的标识;
  4. id() 函数返回对象标识的整数表示。

在==和is之间选择

== 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。算法

  1. is 运算符比 == 速度快,由于它不能重载,因此 Python 不用寻找并调用特殊方法,而是 直接比较两个整数 ID
  2. eq 方法,会考虑对象属性的值。相等性测试可能涉及大量处理工做,例如,比较大型集合或嵌套层级深的结构时。

元组的相对不可变性

元组的不可变性实际上是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关编程

  1. 元组的值会随着引用的可变对象的变化而变。
  2. 元组中不可变的是元素的标识。内存地址
>>> t1 = (1, 2, [30, 40]) ➊
>>> t2 = (1, 2, [30, 40]) ➋
>>> t1 == t2 ➌
True
>>> id(t1[-1]) ➍
4302515784
>>> t1[-1].append(99) ➎
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1]) ➏
4302515784
>>> t1 == t2 ➐
False
基础理解!!!仍是能够的

默认浅复制

>>> l1 = [3, [55, 44], (7, 8, 9)]
>>> l2 = list(l1) ➊
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l2 == l1 ➋
True
>>> l2 is l1 ➌
False

然而,构造方法或 [:] 作的是浅复制(即复制了最外层容器,副本中的元素是源容器中
元素的引用)。若是全部元素都是不可变的,那么这样没有问题,还能节省内存。缓存

为任意对象作深复制和浅复制

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)


bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)

print(id(bus1), id(bus2), id(bus3))

bus1.drop('Bill')
print(bus2.passengers)

print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers))

print(bus3.passengers)

❸ 审查 passengers 属性后发现,bus1 和 bus2 共享同一个列表对象,由于 bus2 是
bus1 的浅复制副本。
❹ bus3 是 bus1 的深复制副本,所以它的 passengers 属性指代另外一个列表。数据结构

注意,通常来讲,深复制不是件简单的事。若是对象有循环引用,那么这个朴素的算法会进入无限循环

深复制

>>> a = [10, 20]
>>> b = [a, 30]
>>> a.append(b)
>>> a
[10, 20, [[...], 30]]
>>> from copy import deepcopy
>>> c = deepcopy(a)
>>> c
[10, 20, [[...], 30]]
深复制有时可能太深了。例如,对象可能会引用不应复制的外部资源或单例值。咱们能够实现特殊方法 __copy__() 和 __deepcopy__(),控制 copy 和 deepcopy 的行为

函数的参数做为引用时

共享传参指函数的各个形式参数得到实参中各个引用的副本。也就是说,函数内部的形参
是实参的别名。app

def f(a, b):
    a += b
    return a


a = [1, 2]
b = [3, 4]

print(f(a, b))
print(a, b)

这里变量全都是引用,不管局部变量仍是全局.
因此上面案例中,a会变化函数

不要使用可变类型做为参数的默认值

class HauntedBus:
    """备受幽灵乘客折磨的校车"""
    def __init__(self, passengers=[]):  #别使用这种可变类型 做为默认参数
        self.passengers = passengers

防护性编程(对待可变类型)

class TwilightBus:
    """正常的校车"""

    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)


bus1 = TwilightBus(("sfs", 'sdf'))
bus2 = TwilightBus(["sdfsdfsfd111"])

bus1.pick("ppxia")
bus1.drop("sfs")
print(bus1.passengers)

bus2.drop("sdfsdfsfd111")
print(bus2.passengers)
http://www.pythontutor.com/vi...
尽可能别用可变类型作默认参数值, 实在要用,必须使其产生副本

del和垃圾回收

  • 有个 del 特殊方法,可是它不会销毁实例,不该该在代码中调用。
  • 即将销毁实例时,Python 解释器会调用 del 方法,给实例最后的机会,释放外资源。
  • 本身编写的代码不多须要实现 del 代码,有些 Python 新手会花时间实现,但却吃力不讨好,由于 del 很难用对。

垃圾计数器

  1. 在 CPython 中,垃圾回收使用的主要算法是引用计数。
  2. 实际上,每一个对象都会统计有多少引用指向本身。
  3. 当引用计数归零时,对象当即就被销毁:CPython 会在对象上调用__del__ 方法(若是定义了),而后释放分配给对象的内存。

为了演示对象生命结束时的情形,示例 8-16 使用 weakref.finalize 注册一个回调函数,在销毁对象时调用。

>>> import weakref
>>> s1 = {1, 2, 3}
>>> s2 = s1 ➊
>>> def bye(): ➋
... print('Gone with the wind...')
...
>>> ender = weakref.finalize(s1, bye) ➌
>>> ender.alive ➍
True
>>> del s1
>>> ender.alive ➎
True
>>> s2 = 'spam' ➏
Gone with the wind...
>>> ender.alive
False

❺ 如前所述,del 不删除对象,而是删除对象的引用。
❻ 从新绑定最后一个引用 s2,让 {1, 2, 3} 没法获取。对象被销毁了,调用了 bye 回
调,ender.alive 的值变成了 False。测试

 弱引用

  • 正是由于有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把对象销毁。可是,有时须要引用对象,而不让对象存在的时间超过所需时间。
  • 弱引用不会增长对象的引用数量。引用的目标对象称为所指对象(referent)。所以咱们说,弱引用不会妨碍所指对象被看成垃圾回收。
  • 弱引用在缓存应用中颇有用,由于咱们不想仅由于被缓存引用着而始终保存缓存对象。

弱引用是可调用的对象,返回的是被引用的对象;

>>> import weakref
>>> a_set = {0, 1}
>>> wref = weakref.ref(a_set) ➊
>>> wref
<weakref at 0x100637598; to 'set' at 0x100636748>
>>> wref() ➋
{0, 1}
>>> a_set = {2, 3, 4} ➌
>>> wref() ➍
{0, 1}
>>> wref() is None ➎
False
>>> wref() is None ➏
True

❷ 调用 wref() 返回的是被引用的对象,{0, 1}。由于这是控制台会话,因此 {0, 1}
会绑定给 _ 变量。
❸ a_set 再也不指代 {0, 1} 集合,所以集合的引用数量减小了。可是 _ 变量仍然指代
它。
❹ 调用 wref() 依旧返回 {0, 1}。
❺ 计算这个表达式时,{0, 1} 存在,所以 wref() 不是 None。可是,随后 _ 绑定到结
果值 False。如今 {0, 1} 没有强引用了。
❻ 由于 {0, 1} 对象不存在了,因此 wref() 返回 None。spa

弱引用到此为止,用到再来查 page 289

总结

  1. 变量的不是盒子,是便利贴(就是c的指针)
  2. ==是值相等 is是(内存地址相等)
  3. 默认是浅复制,就内存地址复制.深复制会有一些过深危险(能够重写特殊方法 __copy__() 和 __deepcopy__())
  4. 尽可能别用可变类型作默认参数值, 实在要用,必须使其产生副本
  5. 实际上,每一个对象都会统计有多少引用指向本身。 Cpython中, 当引用计数归零时,对象当即就被销毁:CPython会在对象上调用__del__ 方法(若是定义了),而后释放分配给对象的内存
相关文章
相关标签/搜索