【问题标题】:Why does `if None.__eq__("a")` seem to evaluate to True (but not quite)?为什么 `if None.__eq__("a")` 似乎评估为 True(但不完全)?
【发布时间】:2019-05-27 18:56:39
【问题描述】:

如果您在 Python 3.7 中执行以下语句,它将(根据我的测试)打印 b

if None.__eq__("a"):
    print("b")

但是,None.__eq__("a") 的计算结果为 NotImplemented

当然,"a".__eq__("a") 的计算结果为 True"b".__eq__("a") 的计算结果为 False

我最初在测试函数的返回值时发现了这一点,但在第二种情况下没有返回任何内容——因此,函数返回了None

这是怎么回事?

【问题讨论】:

    标签: python python-3.x string boolean-expression equivalence


    【解决方案1】:

    这是一个很好的例子,说明为什么不应直接使用 __dunder__ 方法,因为它们通常不适合替代等效运算符;您应该使用== 运算符代替相等比较,或者在这种特殊情况下,在检查None 时,使用is(更多信息请跳至答案底部)。

    你已经完成了

    None.__eq__('a')
    # NotImplemented
    

    返回NotImplemented,因为被比较的类型不同。考虑另一个以这种方式比较具有不同类型的两个对象的示例,例如1'a'。执行(1).__eq__('a') 也不正确,会返回NotImplemented。比较这两个值是否相等的正确方法是

    1 == 'a'
    # False
    

    这里发生的是

    1. 首先,尝试(1).__eq__('a'),返回NotImplemented。这表示不支持该操作,所以
    2. 调用'a'.__eq__(1),它也返回相同的NotImplemented。那么,
    3. 对象被视为不相同,并返回False

    这是一个不错的小 MCVE,它使用一些自定义类来说明这是如何发生的:

    class A:
        def __eq__(self, other):
            print('A.__eq__')
            return NotImplemented
    
    class B:
        def __eq__(self, other):
            print('B.__eq__')
            return NotImplemented
    
    class C:
        def __eq__(self, other):
            print('C.__eq__')
            return True
    
    a = A()
    b = B()
    c = C()
    
    print(a == b)
    # A.__eq__
    # B.__eq__
    # False
    
    print(a == c)
    # A.__eq__
    # C.__eq__
    # True
    
    print(c == a)
    # C.__eq__
    # True
    

    当然,这并不能解释为什么该操作返回 true。这是因为NotImplemented实际上是一个真值:

    bool(None.__eq__("a"))
    # True
    

    同,

    bool(NotImplemented)
    # True
    

    有关哪些值被认为是真值和假值的更多信息,请参阅Truth Value Testingthis answer 上的文档部分。这里值得注意的是,NotImplemented 是真实的,但如果该类定义了一个分别返回 False0__bool____len__ 方法,情况就不同了。


    如果您想要== 运算符的等效功能,请使用operator.eq

    import operator
    operator.eq(1, 'a')
    # False
    

    但是,如前所述,对于 此特定场景,您要检查 None,请使用 is

    var = 'a'
    var is None
    # False
    
    var2 = None
    var2 is None
    # True
    

    与此功能等效的是使用operator.is_:

    operator.is_(var2, None)
    # True
    

    None 是一个特殊的对象,任何时候内存中只存在一个版本。 IOW,它是 NoneType 类的唯一单例(但同一个对象可能有任意数量的引用)。 PEP8 guidelines 明确表示:

    None 等单例的比较应始终使用isis not,绝不是相等运算符。

    总之,对于像None 这样的单例,使用is 进行参考检查更合适,尽管==is 都可以正常工作。

    【讨论】:

      【解决方案2】:

      您看到的结果是由以下事实引起的

      None.__eq__("a") # evaluates to NotImplemented
      

      计算为NotImplementedNotImplemented 的真值记录为True

      https://docs.python.org/3/library/constants.html

      二进制特殊方法(如__eq__()__lt__()__add__()__rsub__()等)应返回的特殊值,表示该操作未针对其他类型实现;可以由就地二进制特殊方法(例如__imul__()__iand__() 等)出于相同目的返回。 它的真值是真的。

      如果你手动调用__eq()__ 方法而不是仅仅使用==,你需要准备好处理它可能返回NotImplemented 并且它的真值为真的可能性。

      【讨论】:

        【解决方案3】:

        正如您已经想到的那样,None.__eq__("a") 的计算结果为 NotImplemented,但是如果您尝试类似的方法

        if NotImplemented:
            print("Yes")
        else:
            print("No")
        

        结果是

        是的

        这表示NotImplementedtrue的真值

        因此问题的结果是显而易见的:

        None.__eq__(something) 产生NotImplemented

        bool(NotImplemented) 的计算结果为真

        所以if None.__eq__("a") 始终为真

        【讨论】:

          【解决方案4】:

          为什么?

          它返回一个NotImplemented,是的:

          >>> None.__eq__('a')
          NotImplemented
          >>> 
          

          但是如果你看这个:

          >>> bool(NotImplemented)
          True
          >>> 
          

          NotImplemented 实际上是一个真值,所以它返回 b,任何 True 都会通过,任何 False 都不会。

          如何解决?

          你必须检查它是否是True,所以更加可疑,如你所见:

          >>> NotImplemented == True
          False
          >>> 
          

          所以你会这样做:

          >>> if None.__eq__('a') == True:
              print('b')
          
          
          >>> 
          

          如您所见,它不会返回任何内容。

          【讨论】:

          • 最直观的答案 - v 值得补充 - 谢谢
          • :) “值得添加”并没有完全捕捉到我想说的(正如你所看到的)——也许“迟来的卓越”才是我想要的——干杯
          • @scharfmn 是吗?我很想知道您认为这个答案增加了什么以前没有涉及的内容。
          • 这里的视觉/复制内容以某种方式增加了清晰度 - 完整演示
          • @scharfmn ... 尽管提示已被删除,但已接受的答案也有。您是否仅仅因为终端提示被懒惰地留在里面而投票?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-08-15
          • 2018-12-03
          • 1970-01-01
          • 2015-04-16
          • 1970-01-01
          • 2014-06-17
          • 2015-03-15
          相关资源
          最近更新 更多