By default, an object is considered true unless its class defines either a bool() method that returns False or a len() method that returns zero, when called with the object. Here are most of the built-in objects considered false: Constants defined to be false: None and False.
In python 0,"",(),{} are considered as False values.
Python assigns boolean values to values of other types. For numerical types like integers and floating-points, zero values are false and non-zero values are true.
Basicly there is no boolean value. The number 0 is considered to be false and all other numbers are considered to be true....
I believe Python has special case handling for sequences of relational operators to make range comparisons easy to express. It's much nicer to be able to say 0 < x <= 5
than to say (0 < x) and (x <= 5)
.
These are called chained comparisons. And that's a link to the documentation for them.
With the other cases you talk about, the parenthesis force one relational operator to be applied before the other, and so they are no longer chained comparisons. And since True
and False
have values as integers you get the answers you do out of the parenthesized versions.
Because
(0 < 0) and (0 == 0)
is False
. You can chain together comparison operators and they are automatically expanded out into the pairwise comparisons.
EDIT -- clarification about True and False in Python
In Python True
and False
are just instances of bool
, which is a subclass of int
. In other words, True
really is just 1.
The point of this is that you can use the result of a boolean comparison exactly like an integer. This leads to confusing things like
>>> (1==1)+(1==1)
2
>>> (2<1)<1
True
But these will only happen if you parenthesise the comparisons so that they are evaluated first. Otherwise Python will expand out the comparison operators.
The strange behavior your experiencing comes from pythons ability to chain conditions. Since it finds 0 is not less than 0, it decides the entire expression evaluates to false. As soon as you break this apart into seperate conditions, you're changing the functionality. It initially is essentially testing that a < b && b == c
for your original statement of a < b == c
.
Another example:
>>> 1 < 5 < 3
False
>>> (1 < 5) < 3
True
>>> 0 < 0 == 0
False
This is a chained comparison. It returns true if each pairwise comparison in turn is true. It is the equivalent to (0 < 0) and (0 == 0)
>>> (0) < (0 == 0)
True
This is equivalent to 0 < True
which evaluates to True.
>>> (0 < 0) == 0
True
This is equivalent to False == 0
which evaluates to True.
>>> 0 < (0 == 0)
True
Equivalent to 0 < True
which, as above, evaluates to True.
Looking at the disassembly (the bytes codes) it is obvious why 0 < 0 == 0
is False
.
Here is an analysis of this expression:
>>>import dis
>>>def f():
... 0 < 0 == 0
>>>dis.dis(f)
2 0 LOAD_CONST 1 (0)
3 LOAD_CONST 1 (0)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP 0 (<)
11 JUMP_IF_FALSE_OR_POP 23
14 LOAD_CONST 1 (0)
17 COMPARE_OP 2 (==)
20 JUMP_FORWARD 2 (to 25)
>> 23 ROT_TWO
24 POP_TOP
>> 25 POP_TOP
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
Notice lines 0-8: These lines check if 0 < 0
which obviously returns False
onto the python stack.
Now notice line 11: JUMP_IF_FALSE_OR_POP 23
This means that if 0 < 0
returns False
perform a jump to line 23.
Now, 0 < 0
is False
, so the jump is taken, which leaves the stack with a False
which is the return value for the whole expression 0 < 0 == 0
, even though the == 0
part isn't even checked.
So, to conclude, the answer is like said in other answers to this question.
0 < 0 == 0
has a special meaning. The compiler evaluates this to two terms: 0 < 0
and 0 == 0
. As with any complex boolean expressions with and
between them, if the first fails then the second one isn't even checked.
Hopes this enlightens things up a bit, and I really hope that the method I used to analyse this unexpected behavior will encourage others to try the same in the future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With