The not
unary operator always returns a bool
value. It needs to, as it needs to return the boolean inverse of whatever value it was passed, which is always a new value (not its argument).
The real odd part of your chart is actually the third column, which shows that the and
operator does return numbers if it's passed them on both sides. You might expect and
to be a boolean operator just like not
, but it's slightly different. The reason is that and
always returns one of its arguments. If the first argument is falsey, that value is what is returned. If the first argument is truthy, the second argument is returned.
You can see this if you test with values other than just 0
and 1
:
print(3 and 2) # prints 2
print([] and [1,2]) # prints []
The or
operator also does this (in a slightly different manner), but that's covered up in your calculation of b
by the not
calls.
This behavior of and
and or
is called short-circuiting. It's big advantage is that it lets Python avoid interpreting expressions it doesn't need to get the value of the boolean expression. For instance, this expression will not call the slow_function
:
result = falsey_value and slow_function()
It also lets you guard expressions that would cause exceptions if they were evaluated:
if node is not None and node.value > x:
...
If the node
variable is None
in that code, evaluating node.value
would give an AttributeError
(since None
doesn't have a value
attribute). But because and
short circuits, the first expression prevents the second expression from being evaluated when it's invalid.