What are the rules regarding chaining of "==" and "!=" in Python - python

This morning, I find myself writing something like:
if (a == b == c):
# do something
And was surprised that it gave me the expected result.
I thought it would behave as:
if ((a == b) == c):
# do something
But it obviously didn't. It seems Python is treating the first statement differently from the second, which is nice but I couldn't find any documentation or explanation regarding this.
I tested and got this:
In [1]: 2 == 2 == 2
Out[1]: True
In [2]: (2 == 2) == 2
Out[2]: False
Would someone care to explain me what are the rules regarding such "chaining" of == (or !=) ?
Thank you very much.

This works with all comparison operators - eg, you can also do:
>>> 4 < 5 < 6
True
>>> 4 < 5 !=2
True
In general, according to the documentation, a op1 b op2 c where op1 and op2 are any of: <, >, !=, ==, <=, >=, is , is not, in or not in will give the same result as:
a op1 b and b op2 c
The docs also say that this can work with arbitrarily many comparisons, so:
>>> 5 != '5' != 'five' != (3+2)
True
Which can be a slightly confusing result sometimes since it seems to say 5 != (3+2) - each operand is only compared with the ones immediately adjacent to it, rather than doing all possible combinations (which mightn't be clear from examples using only ==, since it won't affect the answer if everything defines __eq__ sanely).

As far as I know the example you point out isn't chaining.
2 == 2 == 2 is like (2 == 2) and ( 2 == 2) which turns out to be True and True
while
(2 == 2) == 2 is like (True) == 2

Check here: http://docs.python.org/reference/expressions.html#not-in

Related

What kind of logic does Python uses to handle Boolean expressions?

So I just started learning Python, and while I was just playing with the shell I came around a weird thing.
print(5!=6==True,"=",True) #Gave False=True
print(5!=6==False,"=",True) #Gave False=True
print(5!=6!=True,"=",True) #Gave True=True
Can anyone explain to me why does this happen
the expression a op1 b op2 c is equivalent to (a op1 b) and (b op2 c)
https://docs.python.org/3/reference/expressions.html#comparisons
https://www.geeksforgeeks.org/chaining-comparison-operators-python/
mainly used to enable easy declaration of constraints such as min < x < max
What's interesting about this is it isn't about regular operator precedence. Adding parentheses doesn't give the same result:
>>> 5!=6==True
False
>>> (5!=6)==True
True
>>> 5!=(6==True)
True
The first one is actually parsed as a chained comparison:
5!=6 and 6==True
which is True and False (result False).
The more natural use of chained comparisons is:
x = 5
if 0 < x < 10: # True
which is parsed as:
0 < x and x < 10

== op isn't working as expected, don't know why [duplicate]

When I was looking at answers to this question, I found I didn't understand my own answer.
I don't really understand how this is being parsed. Why does the second example return False?
>>> 1 in [1,0] # This is expected
True
>>> 1 in [1,0] == True # This is strange
False
>>> (1 in [1,0]) == True # This is what I wanted it to be
True
>>> 1 in ([1,0] == True) # But it's not just a precedence issue!
# It did not raise an exception on the second example.
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
1 in ([1,0] == True)
TypeError: argument of type 'bool' is not iterable
Thanks for any help. I think I must be missing something really obvious.
I think this is subtly different to the linked duplicate:
Why does the expression 0 < 0 == 0 return False in Python?.
Both questions are to do with human comprehension of the expression. There seemed to be two ways (to my mind) of evaluating the expression. Of course neither were correct, but in my example, the last interpretation is impossible.
Looking at 0 < 0 == 0 you could imagine each half being evaluated and making sense as an expression:
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
So the link answers why this evaluates False:
>>> 0 < 0 == 0
False
But with my example 1 in ([1,0] == True) doesn't make sense as an expression, so instead of there being two (admittedly wrong) possible interpretations, only one seems possible:
>>> (1 in [1,0]) == True
Python actually applies comparison operator chaining here. The expression is translated to
(1 in [1, 0]) and ([1, 0] == True)
which is obviously False.
This also happens for expressions like
a < b < c
which translate to
(a < b) and (b < c)
(without evaluating b twice).
See the Python language documentation for further details.

Associativity of comparison operators in Python

What is the associativity of comparison operators in Python? It is straightforward for three comparisons, but for more than that, I'm not sure how it does it. They don't seem to be right- or left-associative.
For example:
>>> 7410 >= 8690 <= -4538 < 9319 > -7092
False
>>> (((7410 >= 8690) <= -4538) < 9319) > -7092
True
So, not left-associative.
>>> 81037572 > -2025 < -4722 < 6493
False
>>> (81037572 > (-2025 < (-4722 < 6493)))
True
So it's not right-associative either.
I have seen some places that they are 'chained', but how does that work with four or more comparisons?
Chained comparisons are expanded with and, so:
a <= b <= c
becomes:
a <= b and b <= c
(b is only evaluated once, though). This is explained in the language reference on comparisons.
Note that lazy evaluation means that if a > b, the result is False and b is never compared to c.
Your versions with parentheses are completely different; a <= (b <= c) will evaluate b <= c then compare a to the result of that, and isn't involved at all, so it's not meaningful to compare the results to determine associativity.
python short-circits boolean tests from left to right:
7410>=8690<=-4538<9319>-7092 -> False
7410>=8690 is False. that's it. the rest of the tests is not preformed.
note that
True == 1
False == 0
are both True and apply when you compare the booleans with integers. so when you surround the statement with brackets you force python to do all the tests; in detail:
(((7410>=8690)<=-4538)<9319)>-7092
False <=-4538
False <9319
True >-7092
True
You are making an error with types, when you write 81037572>-2025 then the system thinks of this as True or False and associates it with 1 and 0. It therefore then gives you a comparison with those binary numbers.

What does it mean that Python comparison operators chain/group left to right?

The Python documentation for operator precedence states:
Operators in the same box group left to right (except for
comparisons, including tests, which all have the same precedence and
chain from left to right — see section Comparisons...)
What does this mean? Specifically:
"Operators in the same box group left to right (except for
comparisons...)" -- do comparisons not group left to right?
If comparisons do not group left to right, what do they do instead? Do they "chain" as opposed to "group"?
If comparisons "chain" rather than "group", what is the difference between "chaining" and "grouping"?
What would be some examples to demonstrate that the comparison operators chain from left to right rather than from right to left?
Grouping (this is what non-comparison operators do):
a + b + c means (a + b) + c
Chaining (this is what comparison operators do):
a < b < c means (a < b) and (b < c)
Grouping left to right (this is the way things are grouped):
5 - 2 - 1 means (5 - 2) - 1 == 2
as opposed to grouping right to left (this would produce a different result):
5 - (2 - 1) == 4
Chaining left to right
Chaining is left to right, so in a < b < c, the expression a < b is evaluated before b < c, and if a < b is falsey, b < c is not evaluated.
(2 < 1 < f()) gives the value False without calling the function f, because 2 < 1 evaluates to false, so the second comparison does not need to be performed.
f() > 1 > g() calls f() in order to evaluate the first comparison, and depending on the result, it might or might not need to evaluate the second condition, which requires calling g().
NB. Each operand is evaluated at most once. So in the expression 1 < f() < 2, the function f() is only called once, and the value it gives is used in both comparisons (if necessary).
https://en.wikipedia.org/wiki/Short-circuit_evaluation
In fact, the chain behavior is not so obvious.
a == b == c
although one would expect this to be converted to
a == b and b == c
it is in fact converted into somthing similar to
b == c if a == b else False
which is a bit confusing if one tries to override the behavior of the comparison operators and chain them.

Using a string as an operator?

For example, let's say I have a bunch of assignment statements like this:
if operator == '==':
if a == b:
c += 5
elif operator == '>':
if a > b:
c += 5
elif operator == '<':
if a < b:
c += 5
The if nested if statements and assignments I gave are just examples but in the program I'm writing, they are really long. Just a small change is present where the operators differ, so I don't want to have to repeat the same long piece of code over and over again just for all these conditions. There are too many conditions and the code will get repeated many times..so is there a "faster" way to do this? Can I maybe define a string as an operator? Or any better ways?
How about:
from operator import *
str_ops = {'<':lt,'>':gt,'==':eq} # etc
op = str_ops.get(my_operator) #where my_operator was previously your 'operator'
assert op is not None #or raise your own exception
if op(a,b):
c+=5
And, if you wanted to gracefully handle a bogus operator in my_operator, you can do:
op = str_ops.get(my_operator, lambda x,y: None) #fallback: do-nothing operator
Bonuses to this approach:
ONE if statement. No matter how many operators you're handling.
O(1) behavior, as opposed to O(n) with branching if/elif statements.
The dict is very declarative: this string goes to this operator.
Doesn't Repeat Yourself.
You can use and and or operators effectively, like this
if (operator == '==' and a == b) or (operator == '>' and a > b) \
or (operator == '<' and a < b):
c += 5
As an alternative solution, if you can trust the source of the operator string (or have some way of validating it) you can use eval. But you have to be really careful when using eval; it can be a big security risk if you don't trust the source of the input.
if eval("a %s b" % operator):
c += 5

Categories

Resources