返回介绍

8.2 标识、相等性和别名

发布于 2024-02-05 21:59:47 字数 3775 浏览 0 评论 0 收藏 0

Lewis Carroll 是 Charles Lutwidge Dodgson 教授的笔名。Carroll 先生指的就是 Dodgson 教授,二者是同一个人。示例 8-3 用 Python 表达了这个概念。

示例 8-3 charles 和 lewis 指代同一个对象

>>> charles = {'name': 'Charles L. Dodgson', 'born': 1832}
>>> lewis = charles  ➊
>>> lewis is charles
True
>>> id(charles), id(lewis)  ➋
(4300473992, 4300473992)
>>> lewis['balance'] = 950  ➌
>>> charles
{'name': 'Charles L. Dodgson', 'balance': 950, 'born': 1832}

❶ lewis 是 charles 的别名。

❷ is 运算符和 id 函数确认了这一点。

❸ 向 lewis 中添加一个元素相当于向 charles 中添加一个元素。

然而,假如有冒充者(姑且叫他 Alexander Pedachenko 博士)生于 1832 年,声称他是 Charles L. Dodgson。这个冒充者的证件可能一样,但是 Pedachenko 博士不是 Dodgson 教授。这种情况如图 8-2 所示。

图 8-2:charleslewis 绑定同一个对象,alex 绑定另一个具有相同内容的对象

示例 8-4 实现并测试了图 8-2 中那个 alex 对象。

示例 8-4 alex 与 charles 比较的结果是相等,但 alex 不是 charles

>>> alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}  ➊
>>> alex == charles  ➋
True
>>> alex is not charles  ➌
True

❶ alex 指代的对象与赋值给 charles 的对象内容一样。

❷ 比较两个对象,结果相等,这是因为 dict 类的 __eq__ 方法就是这样实现的。

❸ 但它们是不同的对象。这是 Python 说明标识不同的方式:a is not b。

示例 8-3 体现了别名。在那段代码中,lewis 和 charles 是别名,即两个变量绑定同一个对象。而 alex 不是 charles 的别名,因为二者绑定的是不同的对象。alex 和 charles 绑定的对象具有相同的(== 比较的就是值),但是它们的标识不同。

Python 语言参考手册中的“3.1 Objects, values and types”一节说道:

每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;你可以把标识理解为对象在内存中的地址。is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。

对象 ID 的真正意义在不同的实现中有所不同。在 CPython 中,id() 返回对象的内存地址,但是在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在对象的生命周期中绝不会变。

其实,编程中很少使用 id() 函数。标识最常使用 is 运算符检查,而不是直接比较 ID。接下来讨论 is 和 == 的异同。

8.2.1 在==is之间选择

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

通常,我们关注的是值,而不是标识,因此 Python 代码中 == 出现的频率比 is 高。

然而,在变量和单例值之间比较时,应该使用 is。目前,最常使用 is 检查变量绑定的值是不是 None。下面是推荐的写法:

x is None

否定的正确写法是:

x is not None

is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。而 a == b 是语法糖,等同于 a.__eq__(b)。继承自 object 的 __eq__ 方法比较两个对象的 ID,结果与 is 一样。但是多数内置类型使用更有意义的方式覆盖了 __eq__ 方法,会考虑对象属性的值。相等性测试可能涉及大量处理工作,例如,比较大型集合或嵌套层级深的结构时。

在结束对标识和相等性的讨论之前,我们来看看著名的不可变类型 tuple(元组),它没有你想象的那么一成不变。

8.2.2 元组的相对不可变性

元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。1 如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。

1而 str、bytes 和 array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。

示例 8-5 表明,元组的值会随着引用的可变对象的变化而变。元组中不可变的是元素的标识。

示例 8-5 一开始,t1 和 t2 相等,但是修改 t1 中的一个可变元素后,二者不相等了

>>> 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

❶ t1 不可变,但是 t1[-1] 可变。

❷ 构建元组 t2,它的元素与 t1 一样。

❸ 虽然 t1 和 t2 是不同的对象,但是二者相等——与预期相符。

❹ 查看 t1[-1] 列表的标识。

❺ 就地修改 t1[-1] 列表。

❻ t1[-1] 的标识没变,只是值变了。

❼ 现在,t1 和 t2 不相等。

元组的相对不可变性解释了 2.6.1 节的谜题。这也是有些元组不可散列(参见 3.1 节中的“什么是可散列的数据类型”附注栏)的原因。

复制对象时,相等性和标识之间的区别有更深入的影响。副本与源对象相等,但是 ID 不同。可是,如果对象中包含其他对象,那么应该复制内部对象吗?可以共享内部对象吗?这些问题没有唯一的答案。参见下述讨论。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文