== 和 is 的区别这个问题对于使用过 Python 一段时间开发人员的来说相信不是一个困难的问题。本文将剖析 Python3.6 源码,旨在从实现细节层面把这个问题说清楚,
从字节码看起
我们先来看看 == 和 is 编译后字节码的区别:
1 | In [1]: def test(): |
从字节码可以看出来,is
和 ==
都是交给 COMPARE_OP
来执行的,通过 oparg(== 是 2,is 是 8) 参数执行不同的处理,顺藤摸瓜,我们来到 COMPARE_OP
:
1 | TARGET(COMPARE_OP) { |
COMPARE_OP
将待比较的对象和参数又传入到 cmp_outcome
:
1 | static PyObject * cmp_outcome(int op, PyObject *v, PyObject *w); |
is 比较的本质
先看 cmp_outcome
函数中处理 is
和 is not
的部分:
1 | static PyObject * |
可以看出,is 和 is not 比较的就是 v 和 w 这俩个指针变量!而指针变量本质上是一个内存地址,它在 32 位系统中就是一个 32 整数,在 64 位系统中就是一个 64 位整数。由此我们可以得出一个结论:is 比较的是俩个对象在内存中是否是同一个地址,换句话说,它们是否是同一个对象。
richcompare
继续往下之前,先来了解下 Python 中 richcompare 的概念。其实不单单是 Python,编程语言应该都会提供 <,<=,==,!=,>,>= 这六中比较,在 Python 源码中,它们统称为 richcompare。每一个比较,Python 都提供了一个重载方法和一个参数码,对应关系如下
1 | 符号 重载方法 参数码 |
可以通过重写上面任意一个或多个方法来重载对应的操作符号,
Python 中每个对象都关联一个类型,类型中有一个 tp_richcompare
函数指针来决定对象之间做 rich compare 时的行为,所有对象的基类型提供了一个默认的实现,我们将在后面介绍。
== 比较的本质
== 比较只是 richcompare 的一种,所有 richcompare 的比较最终都是要交给这个对象关联类型的 tp_richcompare
。大部分内置类型,如 int,list,dict 都重写了这个函数,对于用户自建的类型,会优先调用用户重载的方法,没有再调用默认的 tp_richcompare
。这只是大概的逻辑,具体到细节,相同类型对象之间、不同类型对象之间、对象与其基类对象之间的比较又有所差异。
继续往下看 cmp_outcome
是怎么处理 richcompare 的:
1 | static PyObject * |
richcompare 会进入到 PyObject_RichCompare
:
1 | PyObject * |
这个函数主要是对参数的检查,真正做事的是 do_richcompare
:
1 | /* Perform a rich comparison, raising TypeError when the requested comparison |
这里分为几种情况:
- v 和 w 类型不同,w 是 v 的子类,w 如果重载了某个 richcompare 方法,则调用 w 中的 richcompare 方法:
1 | if (v->ob_type != w->ob_type && |
例子:
1 | In [1]: class A: |
- v 和 w 类型不同,或者 w 不是 v 的子类,或者 w 没有相应的 richcompare 方法,如果 v 定义了相应的 richcompare 方法,就调用 v 中相应的 richcompare 方法:
1 | if ((f = v->ob_type->tp_richcompare) != NULL) { |
例子:
1 | In [1]: class A: |
- w 不是 v 的子类,v 中没有定义或者继承相应的 richcompare 方法而 w 中定义了相应的 richcompare 方法,就调用 w 中相应的 richcompare 方法:
1 | if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) { |
例子,接第二种情况的例子:
1 | In [12]: c == d |
以上三种情况总结起来就是:如果 w 是 v 的子类对象,优先调用 w 相应的 richcompare 方法,否则,v 和和 w 中谁有就调用谁的。
- 如果 v 和 w 都没有相应的 richcompare 方法,那么默认的处理是:
1 | switch (op) { |
可以看到如果比较的是 == 和 !=,结果又回到 v 和 w 指针变量的直接比较,和 is 比较的结果相同,否则会引发一个类型错误。
例子:
1 | In [1]: class A: |
object 的默认 richcompare
所有的类的基类object
提供了一个默认的 richcompare 函数:
1 | PyTypeObject PyBaseObject_Type = { |
实现如下:
1 | static PyObject * |
可以看出,对于俩个相同类型的对象而言,== 默认比较的内存地址是否相同,即会否是同一个对象。对于 !=,如果类没有重载 !=(实现 ne),返回 Py_NotImplemented,这时候又回到上面的第 4 种情况,继续比较内存地址。其他比较也是回到上述第 4 中情况,引发类型错误。
例子:
1 | In [1]: class A: |
总结
本文深入源码,剖析了 is 和 == 的区别和联系,总的来说就是:
- is 比较的是俩个对象内存地址是不是一样,即是否是同一个对象
- == 是 richcompare 的一种,除非对象的类型重写了 tp_richcompare,否则默认的 == 比较的也是俩个对象的内存地址,和 is 一致
Python 的常用内置类型如 int,string,list,dict 都有默认实现的 tp_richcompare 实现,这个可以另写一篇文章介绍了。
此外,与 Python2 相比,整个比较的逻辑是做了简化的,这里就不剖析 Python2 了,只提一点,Python2 中用户是可以通过重写 cmp 方法来决定对象之间的比较逻辑的,从 Python 3.0.1 版本后,这个方法被移除了。