5 / 14
Guide Book · Chapter 5

All the operators

Comparison, logical, identity, membership, bitwise, assignment and the walrus, plus precedence and what each operator really calls.

Comparison

OperatorMeaningCalls
== / !=equal / not equal__eq__ / __ne__
< <= > >=ordering__lt__ __le__ __gt__ __ge__

Comparisons chain: a < b < c means a < b and b < c, with b evaluated once.

python
x = 5
print(1 < x < 10, 1 < x > 3, 3 == 3.0)
print("apple" < "banana", [1, 2] < [1, 3], (1, "a") < (1, "b"))   # compared item by item

Logical: and, or, not

and / or short-circuit, and they return one of the operands, not necessarily True/False.

ExpressionReturns
a or ba if a is truthy, else b
a and ba if a is falsy, else b
not aalways a real bool
python
print(0 or "default", "set" or "default")   # default  set
print(0 and 1 / 0)       # 0: the division never runs (short-circuit!)
print(not "", not [0])   # True False

name = ""
print(name or "Anonymous")   # a classic default-value idiom
python
import dis

dis.dis("a or b")

Identity & membership

python
items = [1, 2, 3]
print(2 in items, 5 not in items)
print("py" in "python", "key" in {"key": 1})   # dicts test *keys*
x = None
print(x is None, x is not None)
python
import time

data_list = list(range(1_000_000))
data_set = set(data_list)
for container in (data_list, data_set):
    t = time.perf_counter()
    for _ in range(100):
        999_999 in container
    print(type(container).__name__, f"{(time.perf_counter() - t) * 1000:.2f} ms")

Bitwise

They work on the binary digits of ints (and on sets: | union, & intersection).

OpMeaning0b1100 ? 0b1010
&and0b1000 (8)
|or0b1110 (14)
^xor0b0110 (6)
~xinvert~12 == -13
<< >>shift1 << 4 == 16
python
a, b = 0b1100, 0b1010
print(a & b, a | b, a ^ b, ~a, 1 << 4, 256 >> 2)
print(bin(a & b))

READ, WRITE, EXEC = 1, 2, 4          # permission flags
perms = READ | WRITE
print(bool(perms & WRITE), bool(perms & EXEC))

Assignment operators

x += 1 is augmented assignment. For mutable objects it modifies in place (__iadd__); for immutable ones it builds a new object.

python
a = [1]
b = a
a += [2]        # in place: b sees it
print(b)

s = "x"
t = s
s += "y"        # new string: t does not change
print(t, s)

The walrus operator :=

Assigns and returns a value inside an expression (Python 3.8+).

python
data = [4, 8, 15, 16, 23, 42]
if (n := len(data)) > 5:
    print(f"long list: {n} items")

print([y for x in data if (y := x * 2) > 20])

Unpacking operators * and **

python
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last)
merged = {**{"a": 1}, **{"b": 2}}
print([*range(3), *"ab"], merged)

Precedence (highest first)

LevelOperators
1() [] . calls, indexing, attributes
2** (right-associative: 2**3**2 == 2**9)
3unary +x -x ~x
4* / // % @
5+ -
6<< >>
7&
8^
9|
10comparisons, in, is (all chainable)
11not
12and
13or
14x if c else y
15:=
python
print(-2 ** 2)          # -4: ** binds tighter than unary minus
print(2 ** 3 ** 2)      # 512
print(not 1 == 2)       # True: == happens first
print(1 + 2 * 3, (1 + 2) * 3)
python
class Meters:
    def __init__(self, v):
        self.v = v
    def __add__(self, other):
        if isinstance(other, (int, float)):
            return Meters(self.v + other)
        return NotImplemented
    __radd__ = __add__           # so that 5 + Meters(2) works too
    def __repr__(self):
        return f"{self.v}m"

print(Meters(2) + 5, 5 + Meters(2), 3 * "ab")

🧭 Scenario: Reading a config value that might be missing or empty: timeout = config.get("timeout") or 30. Careful: if 0 is a valid timeout, or would replace it with 30, since 0 is falsy. Use an explicit None check instead: t = config.get("timeout"); timeout = 30 if t is None else t.

main.py
Loading editor…
No output yet
Press Run or ⌘↵ to compile and run your code.

Search

Jump to a lesson or a past run