Python中的相等检查差异
假设我们想要在'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 个回答
这要看情况。你可以自己写一个特别的 __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
可能是真的。
关于整数来说,前两个比较在性能上没有任何区别。
不过,第三个比较就不一样了;因为这涉及到更多的堆栈操作。实际上,代码
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
如果你有更多的变量需要测试,使用 all
可能会让代码看起来更清晰一些:
if all(i==5 for i in [a,b,c,d]):
# do something
因为这两种写法基本上是一样的,所以你可以考虑一下你是怎么阅读或思考这些代码的:
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=0
:a==b==0
是真的,而 (a==b) == 0
就不是!)
手册中说:
Python 中有八种比较操作。它们的优先级相同(比布尔操作的优先级高)。比较可以任意链接;例如,x < y <= z 等价于 x < y 和 y <= z,只是 y 只会被计算一次(但在 x < y 被发现为假时,z 根本不会被计算)。
甚至可能会有差异,比如在你的例子中,如果计算 b
会有副作用。
关于传递性,你说得对。