为什么使用"is"进行布尔比较会变得如此糟糕?

4
样式指南读取如下:
# Correct:
if greeting:

# Wrong:
if greeting == True:

# Worse:
if greeting is True:

参见PEP 8,搜索“worse”一词。

为什么这样做?我习惯于尽可能明确地检查条件,以使代码更易读,并捕获异常。

考虑下面的函数:

def f(do_it):
   if do_it : print("doit")
   else : print("no don't") 

它容易被滥用/监视,并产生意外的行为

>>> f("False")
doit
>>> f([False])
doit

当你检查一个返回值可能无意中通过if语句的情况时,这是一个真正的问题。可以通过使用is结构来避免这种情况。

显然,PEP建议有很好的理由,但是是什么呢?

评论者的进一步研究导致了以下发现:

if x:

调用x所属类的__bool 方法。该方法应根据对象自身认定为True还是False而返回其中之一。

if x==True:

调用 x 类的 __eq 方法。该方法应能将自身与 True(或 False)进行比较,并根据情况返回 True 或 False。

if x is True

这不会调用它们。这个测试检查x是否是"True"对象。它完全绕过了__eq和__bool方法。

注意: 我不是在问"=="和"is"之间的区别。如果您是因为这个问题而来,请参见"=="和"is"之间有什么区别吗?


编程语言在这方面一直都做错了。在我看来,你不应该测试任何不是布尔值或通过显式布尔条件的东西。与此无关的单行if和else语句是被广泛反对的。 - jarmod
boolTrue进行比较是多余和误导性的。它表明“这可能是True,也可能是False,或者可能是其他东西”。它已经是一个bool了。你不需要继续将其与True进行比较。如果x是一个bool,那么以下语句是等效的:if x:if x == True:if (x == True) == True:if ((x == True) == True) == True:等等。只有其中一个是没有无用、误导性垃圾的:if x: - Tom Karzes
你似乎将明确性(好)与无意义的冗长(坏)混淆了。x == True并不比x更明确,它只是毫无意义的冗长。至于在这里使用is,根本没有任何积极的理由,无论是明确性还是其他方面。相反,当你测试一个时,你正在错误地使用引用语义。关于你的例子,如果你想避免类型错误,请使用类型注释和类型检查器。 - Konrad Rudolph
@jarmod:这需要一种静态类型语言,或者在每个“if”和“while”中使用显式比较语法。 - user2357112
@user2357112 无稽之谈,编程语言可以直接禁止这种操作,并在条件未能返回 bool 类型的值时在运行时失败。实际上已经有类似的限制存在,只不过相对宽松一些。这是一个明智决策,而非动态类型的副作用。 - Konrad Rudolph
@KonradRudolph:确实。我在考虑静态禁止它,也许是因为我熟悉的仅有的禁止它的语言都是这样做的。 - user2357112
2个回答

5
因为这是逻辑上的错误,一个范畴错误。使用is时,你明确地执行了一个标识检查,即引用比较。但这不是你在这里想要做的。代码的意图是检查一个值是否为真(或更严格地说,是否为True)。无论该值是否恰好驻留在内存中与常量True相同的地址上,都不仅与此无关,而且会分散注意力。
换句话说,代码的意图涉及表达式的,因此要检查其,而不是其引用标识

2

从理论上讲,is表达的是错误的内容。我们关心的是值的真实性,而不是身份。在极少数情况下,我们确实关心身份,这时使用is比较是适当的。

从实际角度来看,is True甚至不能按人们的期望工作:

In [1]: import numpy

In [2]: x = numpy.array([1, 2, 3])

In [3]: flag = x[0] == 1

In [4]: flag
Out[4]: True

In [5]: if flag: print('true')
true

In [6]: if flag is True: print('true')

In [7]:

我们将1与1进行比较,得到了一个看起来像是True的东西,但是is比较失败了。那是因为bool不是唯一的布尔类型。库可以自由地定义它们自己的类型。 flagnumpy.bool_的实例,它是一个不同于True的对象。(NumPy有很好的理由这样做-使用自己的布尔类型允许它们提供更统一的0维值处理。这也是NumPy拥有自己的数字标量类型的原因。)
此外,is True甚至无法捕获您示例中的问题。它只是将一个静默的错误行为替换为另一个。f(“False”)打印doit是一个问题,但是f(“True”)静默地什么也不做也是一个问题。测试的任何版本都不会产生实际的错误消息。

因为bool不是唯一的布尔类型,这就是关键。此外,“is”不会调用__eq__(用于x==True)或__bool__(用于“if x”),所以它确实更糟糕。 - P2000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接