Looking into Queue.py in Python 2.6, I found this construct that I found a bit strange:

``````def full(self):
"""Return True if the queue is full, False otherwise
(not reliable!)."""
self.mutex.acquire()
n = 0 < self.maxsize == self._qsize()
self.mutex.release()
return n
``````

If `maxsize` is 0 the queue is never full.

My question is how does it work for this case? How `0 < 0 == 0` is considered False?

``````>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
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 parentheses 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)
6 DUP_TOP
7 ROT_THREE
8 COMPARE_OP               0 (<)
11 JUMP_IF_FALSE_OR_POP    23
17 COMPARE_OP               2 (==)
20 JUMP_FORWARD             2 (to 25)
>>   23 ROT_TWO
24 POP_TOP
>>   25 POP_TOP
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.

As other’s mentioned `x comparison_operator y comparison_operator z` is syntactical sugar for `(x comparison_operator y) and (y comparison_operator z)` with the bonus that y is only evaluated once.

So your expression `0 < 0 == 0` is really `(0 < 0) and (0 == 0)`, which evaluates to `False and True` which is just `False`.

maybe this excerpt from the docs can help:

These are the so-called “rich
comparison” methods, and are called
for comparison operators in preference
to `__cmp__()` below. The correspondence
between operator symbols and method
names is as follows: `x<y` calls
`x.__lt__(y)`, `x<=y` calls `x.__le__(y)`,
`x==y` calls `x.__eq__(y)`, `x!=y` and `x<>y`
call `x.__ne__(y)`, `x>y` calls
`x.__gt__(y)`, and `x>=y` calls
`x.__ge__(y)`.

A rich comparison method may return
the singleton `NotImplemented` if it
does not implement the operation for a
given pair of arguments. By
convention, `False` and `True` are
returned for a successful comparison.
However, these methods can return any
value, so if the comparison operator
is used in a Boolean context (e.g., in
the condition of an if statement),
Python will call `bool()` on the value
to determine if the result is true or
false.

There are no implied relationships
among the comparison operators. The
truth of `x==y` does not imply that `x!=y`
is false. Accordingly, when defining
`__eq__()`, one should also define `__ne__()` so that the operators will behave as expected. See the paragraph
on `__hash__()` for some important notes
on creating hashable objects which
support custom comparison operations
and are usable as dictionary keys.

There are no swapped-argument versions
of these methods (to be used when the
left argument does not support the
operation but the right argument
does); rather, `__lt__()` and `__gt__()`
are each other’s reflection, `__le__()`
and `__ge__()` are each other’s
reflection, and `__eq__()` and `__ne__()`
are their own reflection.

Arguments to rich comparison methods
are never coerced.

These were comparisons but since you are chaining comparisons you should know that:

Comparisons can be chained
arbitrarily, e.g., `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).

Formally, if a, b, c, …, y, z are
expressions and op1, op2, …, opN are
comparison operators, then a op1 b op2
c … y opN z is equivalent to a op1 b
and b op2 c and … y opN z, except
that each expression is evaluated at
most once.

Here it is, in all its glory.

``````>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
...
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>>
``````

I’m thinking Python is doing it’s weird between magic. Same as `1 < 2 < 3` means 2 is between 1 and 3.

In this case, I think it’s doing [middle 0] is greater than [left 0] and equal to [right 0]. Middle 0 is not greater than left 0, so it evaluates to false.