Boolean Short-Circuiting
and/or don't return True or False — they return one of their operands, and stop early.
Here is the fact that surprises most people: in Python, and and or do not return a Boolean. They return one of the operands themselves, and they stop evaluating as soon as the answer is known.
a and b→ ifais falsy, returna; otherwise returnb.a or b→ ifais truthy, returna; otherwise returnb.
print(0 and 99) # 0 — stopped at the falsy left side
print(2 and 99) # 99 — left truthy, so returns the right
print('' or 'Guest') # Guest
print('Ada' or 'Guest') # AdaWhy short-circuiting matters
Because the right side may never run, you can use it as a guard: user and user.is_active safely skips the attribute access when user is None. This is the same laziness that makes func() or expensive_default() avoid computing the default unless it is actually needed.
Example
log = []
def cheap():
log.append('cheap')
return 0 # falsy
def pricey():
log.append('pricey')
return 'result'
# 'or' returns the first truthy operand — pricey() runs only because cheap() was falsy
value = cheap() or pricey()
print('value :', value) # result
print('what ran :', log) # ['cheap', 'pricey']
log.clear()
# 'and' stops at the first falsy operand — pricey() is never called
value = cheap() and pricey()
print('value :', value) # 0
print('what ran :', log) # ['cheap'] (short-circuited!)
# A safe navigation guard
def greet(user):
name = user and user.get('name') # None-safe: skips .get if user is None
return f'Hi, {name or "stranger"}'
print(greet({'name': 'Ada'})) # Hi, Ada
print(greet(None)) # Hi, stranger
# Output:
# value : result
# what ran : ['cheap', 'pricey']
# value : 0
# what ran : ['cheap']
# Hi, Ada
# Hi, strangerWhen to use it
- A default value pattern uses 'name or "Guest"' to substitute a fallback when name is empty.
- A guard uses 'user and user.is_active' to avoid an AttributeError when user might be None.
- A permission check short-circuits with 'or' so expensive database lookups are skipped for admins.
More examples
and/or return operands, not booleans
Shows that 'and'/'or' return one of their operands rather than always returning True/False.
print(0 or 'default') # 'default' (0 is falsy, returns right)
print(5 or 'default') # 5 (5 is truthy, returns left)
print(None and 'value') # None (None is falsy, stops)
print(1 and 'value') # 'value' (1 is truthy, returns right)Default value pattern
Uses the 'or' short-circuit to replace a falsy (None or empty) name with a default string.
def greet(name=None):
name = name or 'Guest'
print(f'Hello, {name}!')
greet() # Hello, Guest!
greet('Alice') # Hello, Alice!Safe attribute access with and
Guards against AttributeError when 'user' might be None by short-circuiting with 'and'.
user = None
# Safe: 'and' stops at None, never accesses .name
name = user and user.name
print(name) # None (no AttributeError)
class User:
name = 'Alice'
user = User()
print(user and user.name) # Alice
Discussion