Python中的相等检查差异

43 投票
4 回答
64333 浏览
提问于 2025-04-18 03:45

假设我们想要在'a'和'b'都等于5的时候执行一段代码。我们可以这样写:

if a == 5 and b == 5:
    # Do something

但是几天前,我不小心写了一个类似的条件检查:

if a == b and b == 5:
    # Do something

这让我思考,这两者之间有什么区别吗?还有另外一种写法,

if a == b == 5:
    # Do something

这两种写法在执行的过程、时间上有没有区别?哪种写法更好,或者说用哪种更合适呢?

这和传递性这个概念有关系吗?

4 个回答

7

这要看情况。你可以自己写一个特别的 __eq__ 方法,这样就可以让你的对象和整数等进行比较:

 class NonNegativeInt(object):
   def __init__(self, value):
     if value < 0:
       raise Exception("Hey, what the...")
     self.value = value

   def __eq__(self, that):
     if isinstance(that, int):
       return self.value == that
     elif isinstance(that, NonNegativeInt):
       return self.value == that.value
     else:
       raise ArgumentError("Not an acceptible argument", "__eq__", that)

这样比较的结果会根据你把“b”和“a”比较,或者把“b”和一个整数比较而有所不同。因此,a == b 可能是假的,而 a == 5 and b == 5 可能是真的。

15

关于整数来说,前两个比较在性能上没有任何区别。

不过,第三个比较就不一样了;因为这涉及到更多的堆栈操作。实际上,代码

import dis

def comparison_1(a, b):
    if a == 5 and b == 5:
        pass

def comparison_2(a, b):
    if a == b and b == 5:
        pass

def comparison_3(a, b):
    if a == b == 5:
        pass

print("*** First comparison ***")
dis.dis(comparison_1)

print("\n*** Second comparison ***")
dis.dis(comparison_2)

print("\n*** Third comparison ***")
dis.dis(comparison_3)

返回

*** First comparison ***
  4           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (5)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       27
             12 LOAD_FAST                1 (b)
             15 LOAD_CONST               1 (5)
             18 COMPARE_OP               2 (==)
             21 POP_JUMP_IF_FALSE       27

  5          24 JUMP_FORWARD             0 (to 27)
        >>   27 LOAD_CONST               0 (None)
             30 RETURN_VALUE

*** Second comparison ***
  8           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       27
             12 LOAD_FAST                1 (b)
             15 LOAD_CONST               1 (5)
             18 COMPARE_OP               2 (==)
             21 POP_JUMP_IF_FALSE       27

  9          24 JUMP_FORWARD             0 (to 27)
        >>   27 LOAD_CONST               0 (None)
             30 RETURN_VALUE

*** Third comparison ***
 12           0 LOAD_FAST                0 (a)
              3 LOAD_FAST                1 (b)
              6 DUP_TOP
              7 ROT_THREE
              8 COMPARE_OP               2 (==)
             11 JUMP_IF_FALSE_OR_POP    23
             14 LOAD_CONST               1 (5)
             17 COMPARE_OP               2 (==)
             20 JUMP_FORWARD             2 (to 25)
        >>   23 ROT_TWO
             24 POP_TOP
        >>   25 POP_JUMP_IF_FALSE       31

 13          28 JUMP_FORWARD             0 (to 31)
        >>   31 LOAD_CONST               0 (None)
             34 RETURN_VALUE
21

如果你有更多的变量需要测试,使用 all 可能会让代码看起来更清晰一些:

if all(i==5 for i in [a,b,c,d]):
    # do something
45

因为这两种写法基本上是一样的,所以你可以考虑一下你是怎么阅读或思考这些代码的:

if a == 5 and b == 5:
  # do something

可以理解为“如果 a 等于 5 并且 b 也等于 5,那么就执行...”。你需要思考/得出结论,那么 a 也会等于 b

这和下一个例子是相反的:

if a == b and b == 5:
  # do something 

这个可以理解为“如果 a 等于 b 并且 b 等于 5”,你需要得出结论,那么 a 也会等于 5

这就是我更喜欢最后一个例子的原因:

if a == b == 5:
  # do something

如果你熟悉 Python(感谢 Itzkata),你会立刻明白这三者必须都等于 5。但是,如果一些对 Python 经验较少的人(但在其他语言上有编程技能)看到这个,他们可能会把它理解为

if (a == b) == 5:

这会将第一个比较的布尔结果与整数 5 进行比较,而这并不是 Python 的做法,可能会导致不同的结果(比如考虑 a=0, b=0a==b==0 是真的,而 (a==b) == 0 就不是!)

手册中说:

Python 中有八种比较操作。它们的优先级相同(比布尔操作的优先级高)。比较可以任意链接;例如,x < y <= z 等价于 x < y 和 y <= z,只是 y 只会被计算一次(但在 x < y 被发现为假时,z 根本不会被计算)。

甚至可能会有差异,比如在你的例子中,如果计算 b 会有副作用。

关于传递性,你说得对。

撰写回答