# Boolean Operators and Short-Circuiting

by Christoph Schiessl on Python

Recently, I have written about various topics related to Booleans in Python, such as the built-in `any()` / `all()` functions and the Standard Truth Testing Procedure. But, what I did not yet explain about are three boolean operators themselves:

• Negation with the `not` keyword,
• Conjunction with the `and` keyword, and
• Disjunction with the `or` keyword.

## Negation with the Unary `not` Operator

The `not` operator is the simplest of all the boolean operators. Firstly, it's a unary operator, which takes only a single operand as input. Secondly, the operator's evaluation result is always `True` or `False` (i.e., an instance of the `bool` class). Thirdly, there is no short-circuiting behavior — the whole concept isn't even applicable to unary operators.

``````Python 3.12.2 (main, Feb 17 2024, 11:13:07) [GCC 13.2.1 20230801] on linux
>>> class IAmTrue:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmTrue(label='{self._label}')"
...     def __bool__(self):
...         print("IAmTrue.__bool__() has been called.")
...         return True
...
>>> class IAmFalse:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmFalse(label='{self._label}')"
...     def __bool__(self):
...         print("IAmFalse.__bool__() has been called.")
...         return False
...
>>> not IAmTrue('1st')
IAmTrue.__bool__() has been called.
False
>>> not IAmFalse('1st')
IAmFalse.__bool__() has been called.
True
``````

## Conjunction with the Binary `and` Operator

Next up is the `and` operator, which is more interesting because it's a binary operator, meaning it takes two operands as input. The result is that expressions like `x and y` evaluate to `x` if and only if `x` converts to `False`; otherwise, they evaluate to `y`. This behavior is known as short-circuiting because — as long as the first operand converts to `False` — it doesn't even consider the second operand. Furthermore, note that, in all cases, only the first operand is ever truth-tested!

``````Python 3.12.2 (main, Feb 17 2024, 11:13:07) [GCC 13.2.1 20230801] on linux
>>> class IAmTrue:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmTrue(label='{self._label}')"
...     def __bool__(self):
...         print("IAmTrue.__bool__() has been called.")
...         return True
...
>>> class IAmFalse:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmFalse(label='{self._label}')"
...     def __bool__(self):
...         print("IAmFalse.__bool__() has been called.")
...         return False
...
>>> IAmFalse('1st') and IAmFalse('2nd') # short-circuit to 1st
IAmFalse.__bool__() has been called.
IAmFalse(label='1st')
>>> IAmFalse('1st') and IAmTrue('2nd')  # short-circuit to 1st
IAmFalse.__bool__() has been called.
IAmFalse(label='1st')
>>> IAmTrue('1st') and IAmFalse('2nd')
IAmTrue.__bool__() has been called.
IAmFalse(label='2nd')
>>> IAmTrue('1st') and IAmTrue('2nd')
IAmTrue.__bool__() has been called.
IAmTrue(label='2nd')
``````

## Disjunction with the Binary `or` Operator

Last but not least is the `or` Operator, also a binary operator. The result is that expressions like `x or y` evaluate to `x` if and only if `x` converts to `True`; otherwise, they evaluate to `y`. This is again short-circuiting behavior: As long as the first operand converts to `True`, it doesn't even consider the second operand. Furthermore, note that, in all cases, only the first operand is ever truth-tested!

``````Python 3.12.2 (main, Feb 17 2024, 11:13:07) [GCC 13.2.1 20230801] on linux
>>> class IAmTrue:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmTrue(label='{self._label}')"
...     def __bool__(self):
...         print("IAmTrue.__bool__() has been called.")
...         return True
...
>>> class IAmFalse:
...     def __init__(self, label): self._label = label
...     def __repr__(self): return f"IAmFalse(label='{self._label}')"
...     def __bool__(self):
...         print("IAmFalse.__bool__() has been called.")
...         return False
...
>>> IAmFalse('1st') or IAmFalse('2nd')
IAmFalse.__bool__() has been called.
IAmFalse(label='2nd')
>>> IAmFalse('1st') or IAmTrue('2nd')
IAmFalse.__bool__() has been called.
IAmTrue(label='2nd')
>>> IAmTrue('1st') or IAmFalse('2nd')  # short-circuit to 1st
IAmTrue.__bool__() has been called.
IAmTrue(label='1st')
>>> IAmTrue('1st') or IAmTrue('2nd')   # short-circuit to 1st
IAmTrue.__bool__() has been called.
IAmTrue(label='1st')
``````

## Operator Precedence

Finally, we have to talk about precedence. Here are the three boolean operators ordered from highest to lowest precedence: (1) `not`, (2) `and`, (3) `or`. As always, I want to demonstrate what I'm saying ... so, to prove that `not` has higher precedence than `and`/`or`, we need two boolean variables:

``````for x in [True, False]:
for y in [True, False]:
# Proof: `not` has higher precedence than `and`
assert (not x and y) == ((not x) and y)
assert (x and not y) == (x and (not y))
# Proof: `not` has higher precedence than `or`
assert (not x or y) == ((not x) or y)
assert (x or not y) == (x or (not y))
``````

And three variables to prove that `and` has higher precedence than `or`:

``````for x in [True, False]:
for y in [True, False]:
for z in [True, False]:
# Proof: `and` has higher precedence than `or`
assert (x and y or z) == ((x and y) or z)
assert (x or y and z) == (x or (y and z))
``````

If you run these two scripts, you don't get any `AssertionError`s, proving that precedence works, as I said before. Anyway, that is everything for today! Thank you for reading, and see you soon!

#### Join my Mailing List to receive one article per week.

I send one email per week on building performant and resilient Web Applications with Python, JavaScript and PostgreSQL. No spam. Unscubscribe at any time.

#### Here are a few more Articles for you ...

The Built-In `bool()` Class

Learn about boolean values in Python and the standard truth testing procedure. Understand how objects are converted to `True` or `False`.

By Christoph Schiessl on Python

The Built-In `any()` Function

Learn how to use the built-in `any()` function in Python to determine if any element in an `iterable` is `True`, with implementation and performance insights.

By Christoph Schiessl on Python

Encapsulation: Public, Protected, and Private Members

Learn about encapsulation in Python. This article explains public, protected, and private members and the conventions used to implement them.

By Christoph Schiessl on Python