# Secrets of Logical Operators in Python

04 August

Logical operations play an important role in programming. They are used to create conditional constructs and compose complex algorithms. In Python, logical operations are performed using logical operators:

• `not` — logical negation
• `and` — logical conjunction
• `or` — logical disjunction

In this article, we will talk about the non-obvious details and hidden features of how logical operators work in Python.

## Truth Tables for Logical Operators

We are accustomed to logical operators in programming languages returning `True` or `False` values according to their truth tables.

### Truth Table for the `not` Operator:

`a` `not a`
False True
True False

### Truth Table for the `and` Operator:

`a` `b` `a and b`
False False False
False True False
True False False
True True True

### Truth Table for the `or` Operator:

`a` `b` `a or b`
False False False
False True True
True False True
True True True

When the operands of logical operators are `True` and `False`, the behavior of logical operators in Python also follows these truth tables.

``````print(not True)
print(not False)
print(False and True)
print(True and True)
print(False or True)
print(False or False)
``````

This code outputs:

``````False
True
False
True
True
False
``````

However, Python does not restrict us to just `True` and `False` as operands for logical operators. The operands of the `not`, `and`, and `or` operators can be objects of any other data types.

## Concepts of Truthy and Falsy

One of the important features of Python is the concept of truthy and falsy objects. Any object in Python can be evaluated as `True` or `False`. Objects that evaluate to `True` are called truthy objects, while objects that evaluate to `False` are called falsy objects.

Built-in falsy objects include:

• The value `False`
• The value `None`
• Numeric zeros: `0`, `0.0`, `0j`, `Decimal(0)`, `Fraction(0, 1)`
• Empty sequences and collections: `''`, `()`, `[]`, `{}`, `set()`, `range(0)`

Other objects of built-in data types are truthy objects. Instances of custom classes are also truthy objects by default.

To convert objects to `True` or `False`, the built-in `bool()` function is used.

``````# Falsy objects
print(bool(False))
print(bool(None))
print(bool(0))
print(bool(0.0))
print(bool([]))
print(bool(''))
print(bool({}))

# Truthy objects
print(bool(True))
print(bool(123))
print(bool(69.96))
print(bool('beegeek'))
print(bool([4, 8, 15, 16, 23, 42]))
print(bool({1, 2, 3}))
``````

This code outputs:

``````False
False
False
False
False
False
False
True
True
True
True
True
True
``````

The concept of truthy and falsy objects in Python allows for working with conditional operators in a simpler manner.

For example, the following code:

``````if len(data) > 0:
...

if value == True:
...

if value == False:
...
``````

can be rewritten as:

``````if data:
...

if value:
...

if not value:
...
``````

## The `not` Operator

As we know, the operand of the `not` operator can be an object of any type. If the operand is different from `True` and `False`, it is evaluated according to the concept of truthy and falsy objects. The result of the `not` operator is always `True` or `False`.

``````print(not False)
print(not None)
print(not 0)
print(not 0.0)
print(not [])
print(not '')
print(not {})
``````

This code outputs:

``````True
True
True
True
True
True
True
``````
``````print(not True)
print(not 123)
print(not 69.96)
print(not 'beegeek')
print(not [4, 8, 15, 16, 23, 42])
print(not {1, 2, 3})
``````

This code outputs:

``````False
False
False
False
False
False
``````

## The `and` and `or` Operators

The operands of the `and` and `or` operators, like the `not` operator, can be objects of any data types. Analogous to the `not` operator, you might assume that the result of the `and` and `or` operators is also `True` or `False`. However, these operators return one of their operands. Which one depends on the operator itself.

``````print(None or 0)
print(0 or 5)
print('beegeek' or None)
print([1, 2, 3] or [6, 9])

print(1 or 'beegeek' or None)
print(0.0 or 'habr' or {'one': 1})
print(0 or '' or [6, 9])
print(0 or '' or [])
print(0 or '' or [] or {})
``````

This code outputs:

``````0
5
beegeek
[1, 2, 3]
1
habr
[6, 9]
[]
{}
``````

As we can see, the `or` operator evaluates each operand as a truthy or falsy object but returns not `True` or `False`, but the first truthy object or the last object if no truthy objects are found in the logical expression.

Similarly, the `and` operator behaves in the same way.

``````print(None and 10)
print(5 and 0.0)
print('beegeek' and {})
print([1, 2, 3] and [6, 9])

print(1 and 'beegeek' and None)
print('habr' and 0 and {'one': 1})
print(10 and [6, 9] and [])
``````

This code outputs:

``````None
0.0
{}
[6, 9]
None
0
[]
``````

The `and` operator returns the first falsy object or the last object if no falsy objects are found in the logical expression.

## Logical Operators Are Lazy

Logical operators in Python are lazy. This means that the returned operand is determined by evaluating the truthiness of all operands from left to right until it becomes irrelevant:

• If the left operand of the `or` operator is a truthy object, the overall result of the logical expression is `True`, regardless of the right operand.
• If the left operand of the `and` operator is a falsy object, the overall result of the logical expression is `False`, regardless of the right operand.

This mechanism is called short-circuit evaluation and is used by the interpreter to optimize computations. Consider the following example that demonstrates this behavior.

``````def f():
print('bee')
return 3

if True or f():
print('geek')
``````

This code outputs:

``````geek
``````

The left operand of the `or` operator is a truthy object (`True`), so there is no need to evaluate the right operand, which means calling the `f()` function. Since the function is not called, the output does not include the string `bee`. The overall result of the logical expression is `True`, so the instructions in the conditional block are executed, and we see the string `geek` in the output.

In contrast, the following code:

``````def f():
print('bee')
return 3

if True and f():
print('geek')
``````

This code outputs:

``````bee
geek
``````

The left operand of the `and` operator is a truthy object (`True`), so the right operand needs to be evaluated, which means calling the `f()` function. As a result, the function's instructions are executed, and we see the string `bee` in the output. The function returns the number `3`, which is also a truthy object. Therefore, the overall result of the logical expression is `3`, and the instructions in the conditional block are executed, and we see the string `geek` in the output.

## Priority of Logical Operators

It is important to remember the priority of logical operators. Below, the logical operators are listed in decreasing order of priority (top to bottom):

• `not`
• `and`
• `or`

According to the priority of logical operators, the following code:

``````a = 0
b = 7
c = 10
print(not a and b or not c)        # 7
``````

is equivalent to:

``````a = 0
b = 7
c = 10
print(((not a) and b) or (not c))  # 7
``````

Relative to other Python operators (

for example, arithmetic operators), logical operators have the lowest priority. If you want to change the priority of operators, use parentheses.

For example, the following code:

``````a = 1
b = 2
c = 3
d = 4

print(a + b and c + d)         # 7
``````

is equivalent to:

``````a = 1
b = 2
c = 3
d = 4

print((a + b) and (c + d))     # 7
``````

But the following code:

``````a = 1
b = 2
c = 3
d = 4

print(a + (b and c) + d)       # 5
``````

is equivalent to:

``````a = 1
b = 2
c = 3
d = 4

print(a + (b and c) + d)       # 5
``````

## The Peculiarities of Logical Operators

If the operands of logical operators are complex objects, the result may be unexpected. Let's look at an example:

``````d1 = {'a': 1}
d2 = {'b': 2}
result = d1 or d2
print(result)                  # {'a': 1}
result['a'] += 10
print(d1)                      # {'a': 11}
``````

As we can see, the `or` operator returns the first truthy object (`d1`) and assigns it to the `result` variable. If we then change the value by the key `'a'` in the `result` dictionary, the dictionary `d1` will also change, as both variables refer to the same object.

If we rewrite the example in a more concise way, it becomes clear why this happens:

``````d1 = {'a': 1}
d2 = {'b': 2}
d = d1 if d1 else d2
d['a'] += 10
print(d1)                      # {'a': 11}
``````

In a similar way, the behavior of the `and` operator can be demonstrated:

``````d1 = {'a': 1}
d2 = {'b': 2}
result = d1 and d2
print(result)                  # {'b': 2}
result['b'] += 10
print(d2)                      # {'b': 12}
``````

The `and` operator returns the second object if the first is a truthy object. If the value by the key `'b'` is then changed in the `result` dictionary, the `d2` dictionary will also change, as both variables refer to the same object.

In a more concise form:

``````d1 = {'a': 1}
d2 = {'b': 2}
d = d2 if d1 else d1
d['b'] += 10
print(d2)                      # {'b': 12}
``````

## Summary

In Python, logical operators are used to create conditional constructs and compose complex algorithms. However, they also have non-obvious details and hidden features:

• The result of the `not` operator is always `True` or `False`, but the result of the `and` and `or` operators can be any object.
• The `not` operator inverts the truthiness of the operand, while the `and` operator returns the first falsy object or the last object, and the `or` operator returns the first truthy object or the last object.
• Logical operators in Python are lazy, meaning they only evaluate operands until the overall result of the logical expression becomes evident.
• Logical operators have a specific priority relative to each other and other Python operators.
• The behavior of logical operators can be unexpected when their operands are complex objects, as the operators return references to the objects, not their copies.

Understanding the peculiarities of logical operators in Python allows for writing more effective and efficient code.

##### programmingpythonlogical operatorsboolean
In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.
##### Den W. 2K
I'm a passionate tech enthusiast who loves diving into the world of software, programming, and tech reviews.