深入 Python —— == 和 is 的区别

== 和 is 的区别这个问题对于使用过 Python 一段时间开发人员的来说相信不是一个困难的问题。本文将剖析 Python3.6 源码,旨在从实现细节层面把这个问题说清楚,

从字节码看起

我们先来看看 == 和 is 编译后字节码的区别:

In [1]: def test():
   ...:     a = 1
   ...:     b = 1
   ...:     a == b
   ...:     a is b
   ...:

In [2]: import dis

In [3]: dis.dis(test)
  2           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  3           4 LOAD_CONST               1 (1)
              6 STORE_FAST               1 (b)

  4           8 LOAD_FAST                0 (a)
             10 LOAD_FAST                1 (b)
             12 COMPARE_OP               2 (==)
             14 POP_TOP

  5          16 LOAD_FAST                0 (a)
             18 LOAD_FAST                1 (b)
             20 COMPARE_OP               8 (is)
             22 POP_TOP
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE

从字节码可以看出来,is== 都是交给 COMPARE_OP 来执行的,通过 oparg(== 是 2,is 是 8) 参数执行不同的处理,顺藤摸瓜,我们来到 COMPARE_OP

TARGET(COMPARE_OP) {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *res = cmp_outcome(oparg, left, right);
    Py_DECREF(left);
    Py_DECREF(right);
    SET_TOP(res);
    if (res == NULL)
        goto error;
    PREDICT(POP_JUMP_IF_FALSE);
    PREDICT(POP_JUMP_IF_TRUE);
    DISPATCH();
}

COMPARE_OP 将待比较的对象和参数又传入到 cmp_outcome

 static PyObject * cmp_outcome(int op, PyObject *v, PyObject *w);

is 比较的本质

先看 cmp_outcome 函数中处理 isis not 的部分:

static PyObject *
cmp_outcome(int op, register PyObject *v, register PyObject *w) {
    int res = 0;
    switch (op) {
    case PyCmp_IS:
        res = (v == w);
        break;
    case PyCmp_IS_NOT:
        res = (v != w);
        break;
    ...
}

可以看出,is 和 is not 比较的就是 v 和 w 这俩个指针变量!而指针变量本质上是一个内存地址,它在 32 位系统中就是一个 32 整数,在 64 位系统中就是一个 64 位整数。由此我们可以得出一个结论:is 比较的是俩个对象在内存中是否是同一个地址,换句话说,它们是否是同一个对象

richcompare

继续往下之前,先来了解下 Python 中 richcompare 的概念。其实不单单是 Python,编程语言应该都会提供 <,<=,==,!=,>,>= 这六中比较,在 Python 源码中,它们统称为 richcompare。每一个比较,Python 都提供了一个重载方法和一个参数码,对应关系如下

符号    重载方法    参数码
<       __lt__      0
<=      __le__      1
==      __eq__      2
!=      __le__      3
>       __gt__      4
>=      __ge__      5

可以通过重写上面任意一个或多个方法来重载对应的操作符号,

Python 中每个对象都关联一个类型,类型中有一个 tp_richcompare 函数指针来决定对象之间做 rich compare 时的行为,所有对象的基类型提供了一个默认的实现,我们将在后面介绍。

== 比较的本质

== 比较只是 richcompare 的一种,所有 richcompare 的比较最终都是要交给这个对象关联类型的 tp_richcompare。大部分内置类型,如 int,list,dict 都重写了这个函数,对于用户自建的类型,会优先调用用户重载的方法,没有再调用默认的 tp_richcompare。这只是大概的逻辑,具体到细节,相同类型对象之间、不同类型对象之间、对象与其基类对象之间的比较又有所差异。

继续往下看 cmp_outcome 是怎么处理 richcompare 的:

static PyObject *
cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
	   ...
    default:
        return PyObject_RichCompare(v, w, op);
    }
    v = res ? Py_True : Py_False;
    Py_INCREF(v);
    return v;
}

richcompare 会进入到 PyObject_RichCompare

  PyObject *
  PyObject_RichCompare(PyObject *v, PyObject *w, int op)
  {
      PyObject *res;

      assert(Py_LT <= op && op <= Py_GE);
      if (v == NULL || w == NULL) {
          if (!PyErr_Occurred())
              PyErr_BadInternalCall();
          return NULL;
      }
      if (Py_EnterRecursiveCall(" in comparison"))
          return NULL;
      res = do_richcompare(v, w, op);
      Py_LeaveRecursiveCall();
      return res;
  }

这个函数主要是对参数的检查,真正做事的是 do_richcompare

/* Perform a rich comparison, raising TypeError when the requested comparison
     operator is not supported. */
  static PyObject *
  do_richcompare(PyObject *v, PyObject *w, int op)
  {
      richcmpfunc f;
      PyObject *res;
      int checked_reverse_op = 0;

      /* 第一种情况 */
      ...
      /* 第二种情况 */
      ...
      /* 第三种情况 */
      ...
      /* 第四种情况 */
      ...

      Py_INCREF(res);
      return res;
  }

这里分为几种情况:

  1. v 和 w 类型不同,w 是 v 的子类,w 如果重载了某个 richcompare 方法,则调用 w 中的 richcompare 方法:
if (v->ob_type != w->ob_type &&
      PyType_IsSubtype(w->ob_type, v->ob_type) &&
      (f = w->ob_type->tp_richcompare) != NULL) {
      checked_reverse_op = 1;
      res = (*f)(w, v, _Py_SwappedOp[op]);
      if (res != Py_NotImplemented)
          return res;
      Py_DECREF(res);
  }

例子:

In [1]: class A:
   ...:     pass
   ...:

In [2]: class B(A):
   ...:     def __eq__(self, o):
   ...:         print('eq richcompare in B')
   ...:         return True
   ...:

In [3]: a = A()

In [4]: b = B()

In [5]: a == b
eq richcompare in B
Out[5]: True
  1. v 和 w 类型不同,或者 w 不是 v 的子类,或者 w 没有相应的 richcompare 方法,如果 v 定义了相应的 richcompare 方法,就调用 v 中相应的 richcompare 方法:
if ((f = v->ob_type->tp_richcompare) != NULL) {
      res = (*f)(v, w, op);
      if (res != Py_NotImplemented)
          return res;
      Py_DECREF(res);
  }

例子:

In [1]: class A:
   ...:     def __eq__(self, o):
   ...:         print('eq richcompare in A')
   ...:

In [2]: class B:
   ...:     pass
   ...:

In [3]: class C(A):
   ...:     pass
   ...:

In [4]: class D(B):
   ...:     def __eq__(self, o):
   ...:         print('eq richcompare in D')
   ...:

In [5]: a = A()

In [6]: b = B()

In [7]: c = C()

In [8]: d = D()

In [9]: a == b
eq richcompare in A

In [10]: a == c
eq richcompare in A

In [11]: a == d
eq richcompare in A

  1. w 不是 v 的子类,v 中没有定义或者继承相应的 richcompare 方法而 w 中定义了相应的 richcompare 方法,就调用 w 中相应的 richcompare 方法:
if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) {
    res = (*f)(w, v, _Py_SwappedOp[op]);
    if (res != Py_NotImplemented)
        return res;
    Py_DECREF(res);
}

例子,接第二种情况的例子:

In [12]: c == d
eq richcompare in A

In [13]: b == d
eq richcompare in D

以上三种情况总结起来就是:如果 w 是 v 的子类对象,优先调用 w 相应的 richcompare 方法,否则,v 和和 w 中谁有就调用谁的

  1. 如果 v 和 w 都没有相应的 richcompare 方法,那么默认的处理是:
switch (op) {
case Py_EQ:
    res = (v == w) ? Py_True : Py_False;
    break;
case Py_NE:
    res = (v != w) ? Py_True : Py_False;
    break;
default:
    PyErr_Format(PyExc_TypeError,
                 "'%s' not supported between instances of '%.100s' and '%.100s'",
                 opstrings[op],
                 v->ob_type->tp_name,
                 w->ob_type->tp_name);
    return NULL;
}

可以看到如果比较的是 == 和 !=,结果又回到 v 和 w 指针变量的直接比较,和 is 比较的结果相同,否则会引发一个类型错误。

例子:

In [1]: class A:
   ...:     pass
   ...:

In [2]: class B:
   ...:     pass
   ...:

In [3]: a = A()

In [4]: b = B()

In [5]: a == b
Out[5]: False

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

In [7]: a != b
Out[7]: True

In [8]: a is not b
Out[8]: True

In [9]: a > b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-1e61210fe837> in <module>()
----> 1 a > b

TypeError: '>' not supported between instances of 'A' and 'B'

object 的默认 richcompare

所有的类的基类object 提供了一个默认的 richcompare 函数:

PyTypeObject PyBaseObject_Type = {
    ...
    object_richcompare,                         /* tp_richcompare */
    ...
}

实现如下:

  static PyObject *
  object_richcompare(PyObject *self, PyObject *other, int op)
  {
      PyObject *res;

      switch (op) {

      case Py_EQ:
          /* Return NotImplemented instead of False, so if two
             objects are compared, both get a chance at the
             comparison.  See issue #1393. */
          res = (self == other) ? Py_True : Py_NotImplemented;
          Py_INCREF(res);
          break;

      case Py_NE:
          /* By default, __ne__() delegates to __eq__() and inverts the result,
             unless the latter returns NotImplemented. */
          if (self->ob_type->tp_richcompare == NULL) {
              res = Py_NotImplemented;
              Py_INCREF(res);
              break;
          }
          res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
          if (res != NULL && res != Py_NotImplemented) {
              int ok = PyObject_IsTrue(res);
              Py_DECREF(res);
              if (ok < 0)
                  res = NULL;
              else {
                  if (ok)
                      res = Py_False;
                  else
                      res = Py_True;
                  Py_INCREF(res);
              }
          }
          break;

     default:
          res = Py_NotImplemented;
          Py_INCREF(res);
          break;
      }
      return res;
  }

可以看出,对于俩个相同类型的对象而言,== 默认比较的内存地址是否相同,即会否是同一个对象。对于 !=,如果类没有重载 !=(实现 ne),返回 Py_NotImplemented,这时候又回到上面的第 4 种情况,继续比较内存地址。其他比较也是回到上述第 4 中情况,引发类型错误。

例子:

In [1]: class A:
   ...:     pass
   ...:

In [2]: class B:
   ...:     def __ne__(self, o):
   ...:         print('ne richcompare in B')
   ...:         return False
   ...:

In [3]: a1 = A()

In [4]: a2 = A()

In [5]: a3 = a1

In [6]: a1 == a2
Out[6]: False

In [7]: a1 == a3
Out[7]: True

In [8]: a1 != a2
Out[8]: True

In [9]: b1 = B()

In [10]: b2 = B()

In [11]: b1 == b2
Out[11]: False

In [12]: b1 != b2
ne richcompare in B
Out[12]: False

In [13]: a1 <= a2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-13-7ba1adc4bd61> in <module>()
----> 1 a1 <= a2

TypeError: '<=' not supported between instances of 'A' and 'A'

总结

本文深入源码,剖析了 is 和 == 的区别和联系,总的来说就是:

  • is 比较的是俩个对象内存地址是不是一样,即是否是同一个对象
  • == 是 richcompare 的一种,除非对象的类型重写了 tp_richcompare,否则默认的 == 比较的也是俩个对象的内存地址,和 is 一致

Python 的常用内置类型如 int,string,list,dict 都有默认实现的 tp_richcompare 实现,这个可以另写一篇文章介绍了。

此外,与 Python2 相比,整个比较的逻辑是做了简化的,这里就不剖析 Python2 了,只提一点,Python2 中用户是可以通过重写 cmp 方法来决定对象之间的比较逻辑的,从 Python 3.0.1 版本后,这个方法被移除了。