爱丽丝和白骑士为本章要讨论的内容定了基调。 本章的主题是对象与对象名称之间的区别。 名称不是对象, 而是单独的东西.
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
网友评论