3

Probably some of you might think this is duplicate, and yes, I found a lot of examples similar to this: enter image description here

But Pycharm says that I could simply this:

if y > x and x != -1:
    #  do something

And I did some searching and could not find something similar. My question is it correct to simplify this function like this:

if y > x != -1:
    #  do something

And if so, is it safe and is there is any difference at all between this version and not simplified version except that it's shorter? If it is not correct way to simplify it, what is then?

enigmq
  • 393
  • 5
  • 19
  • 4
    this is equivalent, but not the most readable... – Jean-François Fabre Sep 27 '18 at 07:24
  • 3
    Agreed with @Jean-FrançoisFabre. It may be shorter, but readability is one nice thing about Python, and it seems like a shame to throw it away to save a few characters. Particularly if someone else might end up working with your code and having to rehash your own concerns about its safety and correctness. – kungphu Sep 27 '18 at 07:27
  • What is x and y supposed to be here? – Sayse Sep 27 '18 at 07:29
  • 1
    @Sayse it doesn't matter, it's guaranteed to be equivalent. Here are [the docs](https://docs.python.org/3/reference/expressions.html#comparisons) – juanpa.arrivillaga Sep 27 '18 at 07:33

1 Answers1

6

this is functionnaly equivalent, but when this:

10 < x < 40

is nice to read, mixing different operators to use chained comparisons isn't the best choice.

Is that really the same? let's disassemble to find out:

def f1(x,y):
    if y > x and x != -1:
        return 0

def f2(x,y):
    if y > x != -1:
        return 0

import dis

print("function 1")
dis.dis(f1)
print("function 2")
dis.dis(f2)

result:

function 1
  2           0 LOAD_FAST                1 (y)
              3 LOAD_FAST                0 (x)
              6 COMPARE_OP               4 (>)
              9 POP_JUMP_IF_FALSE       28
             12 LOAD_FAST                0 (x)
             15 LOAD_CONST               3 (-1)
             18 COMPARE_OP               3 (!=)
             21 POP_JUMP_IF_FALSE       28

  3          24 LOAD_CONST               2 (0)
             27 RETURN_VALUE
        >>   28 LOAD_CONST               0 (None)
             31 RETURN_VALUE
function 2
  6           0 LOAD_FAST                1 (y)
              3 LOAD_FAST                0 (x)
              6 DUP_TOP
              7 ROT_THREE
              8 COMPARE_OP               4 (>)
             11 JUMP_IF_FALSE_OR_POP    23
             14 LOAD_CONST               3 (-1)
             17 COMPARE_OP               3 (!=)
             20 JUMP_FORWARD             2 (to 25)
        >>   23 ROT_TWO
             24 POP_TOP
        >>   25 POP_JUMP_IF_FALSE       32

  7          28 LOAD_CONST               2 (0)
             31 RETURN_VALUE
        >>   32 LOAD_CONST               0 (None)
             35 RETURN_VALUE
>>> 

Surprisingly they aren't the same, and the chained version has more instructions.

Not sure what's going on here (some took some more time to explain it better: How do chained comparisons in Python actually work?), but really I'd stick to the and version which shortcuts and is so much readable (think of future maintainers too...).

That said, one interesting thing about chained comparisons would be if the central argument is computed/takes a long time to compute/has a side effect in the computation and you don't want to store it in a variable:

if y > super_long_computation(x) != -1:

In that case, the central argument is only evaluated once. In the case of and, you'd have to store it beforehand.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219