43

Suppose we want some block of code to be executed when both 'a' and 'b' are equal to say 5. Then we can write like:

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

But a few days ago, I just involuntarily wrote a similar condition check as:

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

which made me think, is there any difference between the two? Also, there is one other way,

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

Is there a difference, a difference in terms of process of evaluation or execution or time taken? And also which one is the better or which is better to use?

Is it related to the concept of transitivity?

8
  • 3
    All three will execute in the same amount of time, and are logically equivalent. In terms of execution time, any of the three ways require exactly two equality comparisons between int type. And as far as which is 'better' that's up to the programmer for clarity and readability. Commented Apr 22, 2014 at 12:53
  • 1
    a < b > 5 and a > b == 5 are possible, too (I noticed the docs Jasper quoted say "Comparisons can be chained arbitrarily"). But I wouldn't recommend them. Commented Apr 22, 2014 at 13:37
  • @TimS. "b is between a and 5" seems rather useful actually, and much more readable than a < b and b > 5, but that's just me. I agree that a > b == 5 is a little bit shaky though.. Commented Apr 22, 2014 at 13:38
  • 1
    "b is between a and 5" would be a < b < 5. a < b > 5 is like a < b and 5 < b. I'd only chain </<= operators, or only ==, since those are the clearest operations Commented Apr 22, 2014 at 13:46
  • @Cyber suppose a = 5 and b = 4 then according to 2nd case a == b will be false and next condition will not be checked so there will be 1 less test for this (false) condition i.e. only 1 comparison. So there should be a marginal speed improvement in that case. Please correct me if I am wrong. Commented Apr 22, 2014 at 16:35

4 Answers 4

45

Since they are basically equivalent, you could also consider the way you read/think about the code:

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

can be read as "if a equals 5 and b equals 5, then do ...". You have to think/conclude, that then also a will be equal to b.

This is opposite to the next example:

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

This reads as "if a is equal to b and b equal to 5" and you have to conclude that then also a will be equal to 5

This is why I prefer the last example:

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

If you are familiar with Python (thanks to Itzkata) it is immediately clear that all three things must be equal (to 5). If however people with less experience in Python (but programming skills in other languages) see this, they might evaluate this to

if (a == b) == 5:

which would compare the boolean result of the first comparison with the integer 5, which is not what Python does and might lead to different results (consider for example with a=0, b=0: a==b==0 is true while (a==b) == 0 is not!

The manual says:

There are eight comparison operations in Python. They all have the same priority (which is higher than that of the Boolean operations). Comparisons can be chained arbitrarily; for example, x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).

There might even be a difference, for example if evaulating b in your example would have a side effect.

Regarding transitivity, you are right.

Sign up to request clarification or add additional context in comments.

4 Comments

@Jasper can you please help me here ! suppose a = 5 and b = 4 then according to 2nd case a == b will be false and next condition will not be checked so there will be 1 less test for this (false) condition i.e. only 1 comparison. whereas in all the other cases there are 2 comparisons always. So there should be a marginal speed improvement in that case. Please correct me if I am wrong.
If I understand the manual correctly, a==b==5 is equivalent to a==b and b==5, so with a=5, b=4, the second test b==5 will not be executed. See also Roberto's answer.
Where do you get "There are eight comparison operators in Python." from? I count 6: <, <=, ==, !=, >, >=... which two am I missing?
docs.python.org/3.0/library/stdtypes.html#comparisons the linked manual also considers is and is not.
21

If you have more variables to test, using all might be slightly more readable:

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

1 Comment

Most pythonic for more than 2 variables.
15

As far as integers are concerned, there isn't any difference, in terms of sheer performance, between the first two comparisons.

The third comparison is different, though; since a little more fiddling with the stack gets involved. Indeed, the code

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)

returns

*** 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

1 Comment

What version of Python is this?
7

It depends. You could write your own custom __eq__ which allows you to compare yourself to ints and things:

 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)

which would work different depending on comparing "b" to "a" and "b" to an "int." Hence, a == b could be false while a == 5 and b == 5 could be True.

1 Comment

Sorry for an unprofessional comment, but I think "Hey, what the... duck" would be much more pythonic IMO =). Couldn't help myself.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.