美文网首页
fluent python-第 8 章 面向对象惯用法

fluent python-第 8 章 面向对象惯用法

作者: 时间之友 | 来源:发表于2017-12-04 16:08 被阅读187次

爱丽丝和白骑士为本章要讨论的内容定了基调。 本章的主题是对象与对象名称之间的区别。 名称不是对象, 而是单独的东西.


8.1 变量不是盒子

In [431]: a = [1,2,3]                         # 第一次测试
In [432]: b = a
In [433]: a.append(4)   # a.append并未改变 a 的地址

In [434]: b             # 所以此时 id()可以测试出 a,b地址不变
Out[434]: [1, 2, 3, 4]

In [435]: a = [1,2,3]                         # 第二次测试
In [436]: b = a
In [437]: a += [4,5]    # 同理,地址不变

In [438]: b
Out[438]: [1, 2, 3, 4, 5]

In [439]: a = [1,2,3]                         # 第三次测试
In [440]: b = a
In [441]: a = a + [4,5]    # a 已经发生了变化

In [442]: a
Out[442]: [1, 2, 3, 4, 5]
In [443]: b
Out[443]: [1, 2, 3]

8.2.2 元组的相对不可变性

元组与多数 Python 集合( 列表、 字典、 集, 等等) 一样, 保存的是对象的引用。 如果引用的元素是可变的, 即便元组本身不可变, 元素依然可变。 也就是说, 元组的不可变性其实是指 tuple 数据结构的物理{ 而 str、 bytes 和 array.array 等单一类型序列是扁平的, 它们保存的不是引用, 而是在连续的内存中保存数据本身( 字符、 字节和数字) }1容( 即保存的引用) 不可变, 与引用的对象无关。

>>> t1 = (1, 2, [30, 40])      ➊  t1 不可变, 但是 t1[-1] 可变。
>>> 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

8.3 默认做浅复制

n [470]: 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)
     ...:

In [471]: car = Bus()

In [472]: car.passengers
Out[472]: []

In [473]: car.pick('lily')

In [474]: car.pick('jack')

In [475]: car.passengers
Out[475]: ['lily', 'jack']

接下来,我们将创建一个 Bus 实例( bus1) 和两个副本, 一个是浅复制副本( bus2) , 另一个是深复制副本( bus3) , 看看在 bus1 有学生下车后会发生什么:

>>> import copy
>>> bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
>>> bus2 = copy.copy(bus1)
>>> bus3 = copy.deepcopy(bus1)
>>> id(bus1), id(bus2), id(bus3)
(4301498296, 4301499416, 4301499752) ➊
>>> bus1.drop('Bill')
>>> bus2.passengers
['Alice', 'Claire', 'David'] ➋
>>> id(bus1.passengers), id(bus2.passengers), id(bus3.passengers)
(4302658568, 4302658568, 4302657800) ➌
>>> bus3.passengers
['Alice', 'Bill', 'Claire', 'David'] ➍

❶ 使用 copy 和 deepcopy, 创建 3 个不同的 Bus 实例。
❷ bus1 中的 'Bill' 下车后, bus2 中也没有他了。
❸ 审查 passengers 属性后发现, bus1 和 bus2 共享同一个列表对
象, 因为 bus2 是 bus1 的浅复制副本。
❹ bus3 是 bus1 的深复制副本, 因此它的 passengers 属性指代另一
个列表。


8.4 函数的参数作为引用时

Python 唯一支持的参数传递模式是共享传参( call by sharing) 。共享传参指函数的各个形式参数获得实参中各个引用的副本。 也就是说, 函数内部的形参是实参的别名.
这种方案的结果是, 函数可能会修改作为参数传入的可变对象, 但是无法修改那些对象的标识( 即不能把一个对象替换成另一个对象) 。 示例中有个简单的函数, 它在参数上调用 += 运算符。 分别把数字、 列表和元组传给那个函数, 实际传入的实参会以不同的方式受到影响

>>> def f(a, b):
... a += b
... return a
...
>>> x = 1
>>> y = 2
>>> f(x, y)
3
>>> x, y ➊
(1, 2)
>>> a = [1, 2]
>>> b = [3, 4]
>>> f(a, b)
[1, 2, 3, 4]
>>> a, b ➋       # 列表 a 变了。
([1, 2, 3, 4], [3, 4])
>>> t = (10, 20)
>>> u = (30, 40)
>>> f(t, u)
(10, 20, 30, 40)
>>> t, u ➌       # 元组 t 没变。
((10, 20), (30, 40))

8.5 del和垃圾回收

对象绝不会自行销毁; 然而, 无法得到对象时, 可能会被当作垃圾
回收 —— Python 语言参考手册中“Data Model”一章

del 语句删除名称, 而不是对象。 del 命令可能会导致对象被当作垃圾回收, 但是仅当删除的变量保存的是对象的最后一个引用, 或者无法得到对象时。 重新绑定也可能会导致对象的引用数量归零, 导致对象被销毁。

为了演示对象生命结束时的情形, 示例使用 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

❶ s1 和 s2 是别名, 指向同一个集合, {1, 2, 3}。
❷ 这个函数一定不能是要销毁的对象的绑定方法, 否则会有一个指向
对象的引用。
❸ 在 s1 引用的对象上注册 bye 回调。
❹ 调用 finalize 对象之前, .alive 属性的值为 True。
❺ 如前所述, del 不删除对象, 而是删除对象的引用。
❻ 重新绑定最后一个引用 s2, 让 {1, 2, 3} 无法获取。 对象被销毁
了, 调用了 bye 回调, ender.alive 的值变成了 False。


8.8 本章小结

每个 Python 对象都有标识、 类型和值。 只有对象的值会不时变化。

杂谈

平等对待所有对象
发现 Python 之前, 我学过Java。 我一直觉得 Java 的 == 运算符用着不舒服。 程序员关注的基本上是相等性, 而不是标识, 但是 Java的 == 运算符比较的是对象( 不是基本类型) 的引用, 而不是对象的值。

Python 采取了正确的方式。 == 运算符比较对象的值, 而 is 比较引用。 此外, Python 支持重载运算符, == 能正确处理标准库中的所有对象, 包括 None——这是一个正常的对象, 与 Java 的 null 不同。

In [480]: a = [1,2]

In [481]: b = [1,2]

In [482]: a == b
Out[482]: True

In [483]: a is b
Out[483]: False

In [484]: id(a)
Out[484]: 4564461576

In [485]: id(b)
Out[485]: 4547337032

相关文章

  • fluent python-第 8 章 面向对象惯用法

    爱丽丝和白骑士为本章要讨论的内容定了基调。 本章的主题是对象与对象名称之间的区别。 名称不是对象, 而是单独的东西...

  • fluent python- 第 5 章 一等函数-面向对象(附

    第 4 章 文本和字节序列 人类使用文本, 计算机使用字节序列。 第 5 章 一等函数 前言: 在 Python ...

  • 第8章 面向对象

    8.1 类的定义与使用 8.1.1 类定义 Python 使用 class 关键字来定义类。类名的首字母一般使用大...

  • Python3.5笔记——第8章 面向对象编程

    Python3.5笔记 第8章 面向对象编程 面向对象术语介绍 类:用来描述具有相同属性和方法的对象的集合。类定义...

  • 1.6 面向对象

    第 1章 面向对象思想 1.1 面向对象思想概述 概述 Java语言是一种面向对象的程序设计语言,而面向对象思想是...

  • Python-面向对象

    类,对象,列表 用Python写一个用类创建房子和床,把床存到房子里,报出一共放置了多少张床。1.在下面代码中“b...

  • python-面向对象

    面向对象 面向过程:根据业务逻辑从上到 下写代码面向对象(OO):将数据与函数绑定到一起,进行封装,这样能够更快速...

  • python-面向对象

    先调用new方法来创建对象,然后找了一个变量来接受new的返回值,这个返回值表示穿件出来的对象的应用。 再调用in...

  • python-面向对象

    面向对象 一种认识世界,分析世界的方法论。将万事万物抽象为类 类class 类是抽象的概念,是万事万物的抽象,是一...

  • python-面向对象

    1、概念 (1)面向过程编程是一种以事件为中心的编程思想,分析解决问题的步骤,然后使用函数或语句块把这些步骤一步一...

网友评论

      本文标题:fluent python-第 8 章 面向对象惯用法

      本文链接:https://www.haomeiwen.com/subject/iuwtixtx.html