Bitwise operations and advanced boolean patterns
Use flags, bitmasks, any/all, walrus, and custom truth-value testing to write expressive, efficient boolean logic.
- Apply bitwise operators to test and manipulate permission flags
- Use any() and all() for concise collection-level boolean tests
- Implement __bool__ and __len__ for custom truth-value testing
- Apply the walrus operator in boolean contexts to avoid re-evaluation
Boolean logic in Python runs deeper than and/or/not. This lesson covers
the bitwise layer — where individual bits encode flags and masks — and the
collection-level tools (any, all) and language features (:=, __bool__)
that let you express complex boolean logic in one readable line instead of many
nested if blocks.
Bitwise operators
Each operator works on individual bits of integer values:
| Operator | Name | Example | Result |
|---|---|---|---|
& | AND | 0b1100 & 0b1010 | 0b1000 |
| | OR | 0b1100 | 0b1010 | 0b1110 |
^ | XOR | 0b1100 ^ 0b1010 | 0b0110 |
~ | NOT (invert) | ~0b0001 | -2 (two's complement) |
<< | Left shift | 1 << 3 | 8 |
>> | Right shift | 16 >> 2 | 4 |
a = 0b1100 # 12
b = 0b1010 # 10
print(bin(a & b)) # 0b1000 (8)
print(bin(a | b)) # 0b1110 (14)
print(bin(a ^ b)) # 0b0110 (6)
print(1 << 4) # 16 (2^4)Flags and bitmasks
The canonical use of bitwise ops is permission flags — compact sets where each bit represents one boolean attribute:
READ = 0b001 # 1
WRITE = 0b010 # 2
EXEC = 0b100 # 4
# Grant read + write
perms = READ | WRITE # 0b011 = 3
# Test for a flag
has_write = bool(perms & WRITE) # True
has_exec = bool(perms & EXEC) # False
# Set a flag
perms = perms | EXEC # add execute
# Clear a flag with bitwise AND + NOT
perms = perms & ~WRITE # remove writeThis pattern appears in OS permissions (os.stat mode bits), network protocol
headers, game entity-component systems, and event-driven libraries everywhere.
Implement has_flag(value, flag), set_flag(value, flag), and clear_flag(value, flag) using bitwise operators. has_flag returns True if the flag bit is set. set_flag returns value with the flag bit set. clear_flag returns value with the flag bit cleared.
has_flag(0b011, 0b010) → Trueclear_flag(0b011, 0b001) → 2any() and all() for collection-level tests
Instead of a manual loop with an accumulator, use the built-ins:
scores = [85, 92, 78, 95, 61]
# all() short-circuits on the first False
if all(s >= 60 for s in scores):
print("everyone passed")
# any() short-circuits on the first True
if any(s >= 90 for s in scores):
print("at least one high achiever")
# both return True on an empty iterable (all) / False (any) — consistent
print(all(s > 0 for s in [])) # True (vacuous truth)
print(any(s > 0 for s in [])) # Falseany/all accept any iterable, compose naturally with generators, and avoid
building an intermediate list.
Write all_valid(records) that returns True only if every record dict has a non-empty "name" (str) and a non-negative "score" (int or float). Use all() and any().
all_valid([{"name": "Alice", "score": 90}]) → Trueall_valid([{"name": "", "score": 90}]) → FalseCustom truth-value testing: bool and len
When Python evaluates an object in a boolean context (if obj:, while obj:,
not obj), it calls __bool__ first, then __len__ as a fallback. Implementing
these makes your objects feel idiomatic:
class Buffer:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
# __bool__ is called first; falls back to len(obj) != 0 if absent
def __bool__(self):
return len(self.data) > 0
buf = Buffer(b"")
print(bool(buf)) # False — empty buffer is falsy
print(bool(Buffer(b"\x00"))) # True — one byte, truthyThe pattern appears throughout the standard library: an empty list, dict,
str, or bytes is falsy; non-empty is truthy. Your own containers should do
the same.
The walrus operator in boolean contexts
:= (walrus, "assignment expression") assigns and returns a value in a single
expression. It shines in loops and conditions where you'd otherwise compute
something twice:
import re
# without walrus: compute match, then check, then use
match = re.search(r"\d+", line)
if match:
print(match.group())
# with walrus: assign and test in one go
if m := re.search(r"\d+", line):
print(m.group())
# very clean in while loops reading chunks from a file
with open("data.bin", "rb") as f:
while chunk := f.read(4096):
process(chunk)Use walrus only where the double-evaluation problem is real — don't reach for it just because it looks clever. A plain two-liner is clearer when the expression is side-effect-free and cheap to compute.
Where to go next
You've handled bytes, stored data, and mastered boolean logic. The last piece of any real pipeline is navigating the filesystem. Next: Filesystem and path manipulation.