Yes, this behavior is what Python expected precisely by the way he will analyze the expression, but such behaviour has nothing to do with the order of precedence of the operators.
What happens is that in Python there is a syntactic sugar for boolean expressions when used two operators as in the problem cited. The actual expression analyzed by Python will actually be the two operators executed independently, repeating the central operand, uniting the results by a logical operation and
. In other words, an expression like A <op1> B <op2> C
, being A
, B
and C
the operands and <op1>
and <op2>
operators, the expression analysed will be (A <op1> B) and (B <op2> C)
. In this case, by:
if 9 in values == False:
print("9 não pertence à lista.")
else:
print("9 pertence à lista.")
What happens, in fact, will be:
if (9 in values) and (values == False):
print("9 não pertence à lista.")
else:
print("9 pertence à lista.")
Where 9 in values
returns false and values == False
returns false; therefore, the final result will also be false, running the block in else
.
In this particular case really the result seems to be quite strange, but this syntactic sugar is especially useful, for example, to check whether a certain value belongs to a range:
if 0 < x < 10:
print("x está entre 0 e 10")
else:
print("x é menor que 1 ou maior que 9")
Such a code would be the equivalent of doing:
if 0 < x and x < 10:
print("x está entre 0 e 10")
else:
print("x é menor que 1 ou maior que 9")
Or equivalent to the traditional form:
if x > 0 and x < 10:
print("x está entre 0 e 10")
else:
print("x é menor que 1 ou maior que 9")
This behavior explains why the first form presented in the question is ideal in Python (mode pythonic):
if 9 not in values:
print("9 não pertence à lista.")
else:
print("9 pertence à lista.")
TL;DR
One way to verify this behavior is to analyze the bytecode generated by Cpython. For this, we can use the native library dis
. For the sake of simplification, we will consider a somewhat simpler expression, which reproduces the same behaviour as the problem addressed:
a < b < c
Where we will try to show that the analyzed expression will be (a < b) and (b < c)
.
We get the analysis of bytecode of this expression making:
import dis
print(dis.dis("a < b < c"))
In which the result will be:
1 0 LOAD_NAME 0 (a)
2 LOAD_NAME 1 (b)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 0 (<)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_NAME 2 (c)
14 COMPARE_OP 0 (<)
16 RETURN_VALUE
>> 18 ROT_TWO
20 POP_TOP
22 RETURN_VALUE
In the documentation itself we can obtain the details of each operation:
LOAD_NAME
puts the value associated with the name a
in the pile;
LOAD_NAME
puts the value associated with the name b
in the pile;
DUP_TOP
duplicates the reference at the top of the stack;
ROT_THREE
moves the second and third stack values one position and moves the top to position 3;
COMPARE_OP
performs the operation <
between the two values at the top of the stack, removing them, and adding the result;
JUMP_IF_FALSE_OR_POP
sets a conditional deviation based on the stack top value: if the value is false, the execution jumps to the line indicated by >>
(item 10 from this list), otherwise the top value of the stack is removed and the run continues;
LOAD_NAME
puts the value associated with the name c
in the pile;
COMPARE_OP
performs the operation <
between the two values at the top of the stack, removing them, and adding the result;
RETURN_VALUE
returns the value from the top of the stack;
ROT_TWO
exchange the two values at the top of the stack;
POP_TOP
discards the top of the stack;
RETURN_VALUE
returns the value from the top of the stack;
The jump that can occur between items 6 and 10 is what we call short-circuit of a logical expression.
Analyzing the bytecode of the question
For the case of the question, let us consider the expression:
9 in values == False
The bytecode is almost identical to the previous one (which shows that the previous expression actually reproduces the same behavior):
1 0 LOAD_CONST 0 (9)
2 LOAD_NAME 0 (values)
4 DUP_TOP
6 ROT_THREE
8 COMPARE_OP 6 (in)
10 JUMP_IF_FALSE_OR_POP 18
12 LOAD_CONST 1 (False)
14 COMPARE_OP 2 (==)
16 RETURN_VALUE
>> 18 ROT_TWO
20 POP_TOP
22 RETURN_VALUE
The only differences, in fact, are that for the values 9
and False
operations are executed LOAD_CONST
, for being constant, and no longer LOAD_NAME
, relating to variables.
Running, we have:
Add constant value 9 to stack;
| Stack
--+--------
1 | 9
--+--------
2 |
--+--------
3 |
Adds the value values
stacked;
| Stack
--+--------
1 | 9
--+--------
2 | values
--+--------
3 |
Duplicate the top value of the stack;
| Stack
--+--------
1 | 9
--+--------
2 | values
--+--------
3 | values
Climbs one position the second and third values, moving the top to the third position;
| Stack
--+--------
1 | values
--+--------
2 | 9
--+--------
3 | values
Executes the operator in
between the top two values 9 in values
, stacking the result;
| Stack
--+--------
1 | values
--+--------
2 | False
--+--------
3 |
If the top of the stack is false, skip the execution (stack remains unchanged);
Swap the top two values of the stack;
| Stack
--+--------
1 | False
--+--------
2 | values
--+--------
3 |
Discards value from top of stack;
| Stack
--+--------
1 | False
--+--------
2 |
--+--------
3 |
Returns the value from the top of the stack False
;
Thus it is possible to clearly understand the reason for the else
be executed in the problem and that the second operator, ==
, is not even analyzed, due to the short circuit that occurs in the logical expression.
It is worth noting that even if there is no explicit operator and
being executed, the behavior "return the first operand if it is false, if it does not return the second" is the natural behaviour of the operator and
, which is why it is said that the expression is evaluated as if it existed and
between the expressions.
Interesting readings
How the Python 'in' operator works
Logic operations in Python 2.7
I created the question because I thought it was a situation at least curious that could generate confusion for beginners, especially when coming from other languages, so it is expected that the answers are as complete as possible.
– Woss
The only thing that comes to mind is order of precedence. If you protect the 9 in values with parentheses gives the same thing?
– João Victor
@Joãovictor In parentheses produces the expected result.
– Jéf Bueno
@Joãovictor the answer would be no and yes, respectively. The problem is not the order of precedence, but if you put the parentheses the result is expected.
– Woss
The problem is that the order of precedence is that the == operator is always executed before the in operator. It is as if you tried to add a number to then divide and not protect the sum with parentheses ( the division would occur first ). What happens is that the code in question checks if values is false before executing "9 in values". What causes the clause to return true.
– João Victor
@Joãovictor was answered this already, but the answer was deleted because that’s not the problem. If you want, access the chat that I can explain better the reason other than that (not to extend the discussion here).
– Woss
@Joãovictor See the deleted reply and comments.
– Jéf Bueno
The operator "==" does not have the same precedence as the operator "in"... This is for its first justification. The second about making a mistake of trying to iterate for a boolean I still can’t explain. But I’ll look for know and answer here.
– João Victor
@Joãovictor See the official documentation with the table of precedence.
– Woss
Ah ok, was python used 3? Sorry my ignorance, I tested in python 2.7.
– João Victor
@Joãovictor same thing.
– Woss
Actually the documentation says the same order of precedence, but if you keep testing prints with these expressions you don’t come to that conclusion. I haven’t read the documentation, but I imagine you have some exception. I can’t explain more than that. If someone answers, I will come back here to read.
– João Victor