如果我在 Python 中调用“None == x”,“幕后”会发生什么?

What happens "behind the scenes" if I call `None == x` in Python?

提问人:Markus Weninger 提问时间:12/9/2022 最后编辑:Markus Weninger 更新时间:12/18/2022 访问量:295

问:

我正在学习和玩 Python,我想出了以下测试代码(请注意,我不会写这样的高效代码,但在学习新语言时,我喜欢玩弄语言的极端情况):

a = None    
print(None == a) # I expected True, I got True

b = 1
print(None == b) # I expected False, I got False

class MyNone:
    # Called if I compare some myMyNone == somethingElse
    def __eq__(self, __o: object) -> bool:
        return True

c = MyNone()
print (None == c) # !!! I expected False, I got True !!!

请参阅代码示例的最后一行。

怎么可能,如果某物显然不是,就会返回?我本来会期望 的结果,但不是 。None == somethingNoneTruesomething == NoneNone == something

我以为它会在幕后打电话。None is something

所以我认为问题归结为:None 单例对象的 __eq__ 方法是什么样子的,我怎么能找到它?


PS:我知道PEP-0008及其报价

与像 None 这样的单例进行比较时,应始终使用 is 或 is not,而不是相等运算符。

我仍然想知道为什么在上面的例子中返回.print (None == c)True

python-3.x 相等 性 nonetype 比较运算符

评论

2赞 Filip Haglund 12/9/2022
非常确定使用了左参数实现,请尝试eqc == None
0赞 Markus Weninger 12/9/2022
@FilipHaglund 我问题的全部意义在于我没有写(这将直接称为“我的方法”),而是.c == None__eq__None == c
0赞 Kelly Bundy 12/9/2022
“How does the __eq__ method of the None singleton object” - 你为什么不试试呢?
0赞 Markus Weninger 12/9/2022
@KellyBundy 这就是我所做的,我尝试过,我无法理解它,这就是这个问题存在的原因。
0赞 Kelly Bundy 12/9/2022
结果如何?

答:

7赞 VPfB 12/9/2022 #1

如文档中所述:调用,但返回除本身以外的任何内容(缺少部分:为什么?我不知道),所以 Python 尝试以相反的方式进行比较,并从 which 总是返回 .x==yx.__eq__(y)None.__eq__(...)NotImplementedNone__eq__MyNoneTrue

Update:(类)不定义自己的,而是使用基于测试的默认值:如果参数相同或其他,则返回。(感谢合著者)NoneNoneType__eq__object.__eq__isTrueisNotImplemented

评论

1赞 bereal 12/9/2022
NotImplementedraised from 的意思与它不存在的意思相同,这允许解释器回退到右侧检查。__eq__
0赞 Markus Weninger 12/9/2022
您能否指出文档中描述/提及“如果返回,Python 会以相反方式尝试比较”部分的地方?这是我所缺少的重要知识。谢谢!NotImplemented
3赞 kaya3 12/9/2022
缺少的部分是 simply ,只有当两个输入是同一个对象(按标识,而不是按值)时,它才会返回。但实际上,实现该行为的源代码部分在此代码中没有到达,因为它的优先级低于右侧的方法(当它有方法时)。有关详细信息,请参阅我的答案。type(None).__eq__object.__eq__True__eq__
0赞 Rohit Lal 12/9/2022
@VPfB总是返回不匹配的数据类型,因此 with 和其他任何东西总是返回,不是吗?__eq__NotImplemented__eq__NoneNotImplemented
1赞 kaya3 12/9/2022
@Rohit 不完全是;是否返回取决于你使用的实现,并且某些实现在调用不同的类型时会返回 OR 结果,例如 或。通常意味着另一种类型没有充分的相关性,无法通过该实现进行比较,或者另一种类型是子类型,应改用其实现。__eq__NotImplemented__eq____eq__TrueFalsefloat.__eq__(1.0, 1)float.__eq__(1.0, 2)NotImplemented__eq____eq__
8赞 kaya3 12/9/2022 #2

其实,的类型并没有自己的方法;在 Python 中,我们可以看到它显然是从基类继承的:None__eq__object

>>> type(None).__eq__
<slot wrapper '__eq__' of 'object' objects>

但这并不是源代码中真正发生的事情。的实现可以在 CPython 源代码的 Objects/object.c 中找到,我们看到:None

PyTypeObject _PyNone_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "NoneType",
    0,
    0,
    none_dealloc,       /*tp_dealloc*/ /*never called*/
    0,                  /*tp_vectorcall_offset*/
    0,                  /*tp_getattr*/
    0,                  /*tp_setattr*/
    // ...
    0,                  /*tp_richcompare */
    // ...
    0,                  /*tp_init */
    0,                  /*tp_alloc */
    none_new,           /*tp_new */
};

我省略了大部分不相关的部分。这里重要的是 's 是 ,即一个空指针。这在 do_richcompare 函数中检查:_PyNone_Typetp_richcompare0

    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }
    if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }

为那些不会说 C 语言的人翻译:

  • 如果左侧的函数不为 null,则调用它,如果其结果不是,则返回该结果。tp_richcompareNotImplemented
  • 否则,如果尚未检查反向*,并且右侧的函数不为 null,则调用它,如果结果不是,则返回该结果。tp_richcompareNotImplemented

代码中还有一些其他分支,如果这些分支都没有返回结果,则可以回退到这些分支。但这两个分支足以看出发生了什么。不是返回,而是该类型在C源代码中根本没有相应的函数。这意味着第二个分支被占用,因此您观察到的结果。type(None).__eq__NotImplemented

*如果已经检查了反向,则设置了标志checked_reverse_op;如果右侧是左侧的严格子类型,则会发生这种情况,在这种情况下,它优先。这不适用于这种情况,因为 type(None) 和您的类之间没有子类型关系。

评论

0赞 kaya3 12/9/2022
@KellyBundy 有趣的问题,但我认为这个问题的答案应该和这个答案一样长。我不是 100% 确定,但我相信在其他地方定义,这就是你抬头时会得到的,因为它不会覆盖它;但是运算符不会直接查找,而是调用由为普通类定义的函数,但为内置类型硬编码的函数。object.__eq__type(None).__eq__==__eq__tp_richcompare__eq__
0赞 Markus Weninger 12/9/2022
这是一个完美的低级解释,谢谢!
0赞 Kelly Bundy 12/9/2022
对不起,我看错了(希望我删除得足够快)。我以为您正在显示 C 代码,即没有方法(因为您正在谈论它并且因为它在 ).objectobject__eq__object.c
0赞 kaya3 12/9/2022
@KellyBundy我认为这是一个相关的问题。需要明确的是,我也不认为有函数,我认为查找机制是一个单独的东西,它从用户定义类型的运算符中调用,但不为内置类型调用。但是我不确定与该类对应的C源代码在哪里。objecttp_richcompare__eq__==object
0赞 chepner 12/9/2022
再往下,如果不指向适当的函数,它将实现比较运算符的默认行为:和 的对象标识,以及其他函数。do_richcomparetp_richcompare==!=TypeError