Short-Circuit Evaluation¶
Python's and and or operators use short-circuit evaluation, stopping evaluation as soon as the result is determined. This behavior is crucial for writing efficient code and preventing errors from evaluating unnecessary expressions.
Mental Model
Think of and/or as lazy gatekeepers. and stops at the first failure because one False makes the whole expression False. or stops at the first success because one True makes the whole expression True. This laziness is not just an optimization -- it lets you write guard patterns like x and x.method() that safely skip the second part when the first is falsy.
and Short-Circuiting¶
Early Termination with and¶
```python def test_value(x): print(f"Testing {x}") return x > 5
result = False and test_value(10) print(f"Result: {result}") ```
Output:
Result: False
Notice that test_value was never called because the first operand is False.
Practical Usage¶
```python user = {"name": "Alice", "age": 30}
if user and user.get("name") and len(user.get("name")) > 0: print(f"Valid user: {user['name']}") ```
Output:
Valid user: Alice
or Short-Circuiting¶
Early Termination with or¶
```python def get_default(): print("Getting default") return "default"
result = "custom" or get_default() print(f"Result: {result}") ```
Output:
custom
The get_default function is never called because "custom" is truthy.
Default Value Pattern¶
```python username = None fallback = "guest"
displayed_name = username or fallback print(f"Username: {displayed_name}") ```
Output:
Username: guest
Efficiency Implications¶
Avoiding Expensive Operations¶
```python import time
def expensive_check(): time.sleep(0.1) return True
Short-circuits immediately¶
result = False and expensive_check() print(f"Instant result: {result}")
This would wait for expensive_check¶
result2 = True and expensive_check() print(f"Takes time: {result2}") ```
Output:
Instant result: False
Takes time: True
Complex Expressions¶
Order Matters¶
```python def check_condition(n, label): print(f"Checking {label}") return n > 5
Only first is checked¶
result = check_condition(3, "A") and check_condition(10, "B") print(f"Result: {result}\n")
Both are checked¶
result = check_condition(10, "A") and check_condition(3, "B") print(f"Result: {result}") ```
Output: ``` Checking A Result: False
Checking A Checking B Result: False ```
Exercises¶
Exercise 1. Without running the code, predict which functions get called and what the final output is.
```python def a(): print("a called") return True
def b(): print("b called") return False
def c(): print("c called") return True
result = a() or b() or c() print(f"Result: {result}") ```
Solution to Exercise 1
```
a called
Result: True
```
a() returns True. Since or short-circuits on the first truthy value, b() and c() are never called. The result is True (the return value of a()).
Exercise 2.
Write a function safe_divide(a, b) that returns a / b if b is not zero, or "undefined" otherwise. Use short-circuit evaluation with and/or in a single expression (no if statement).
Solution to Exercise 2
```python
def safe_divide(a, b):
return b and a / b or "undefined"
# Caution: this fails if a/b is 0 (falsy). A safer version:
def safe_divide(a, b):
return a / b if b else "undefined"
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # "undefined"
```
The and/or approach works for most cases but has an edge case: if a / b evaluates to 0 or 0.0 (falsy), it would incorrectly return "undefined". The conditional expression version is safer and more readable.
Exercise 3.
Explain why the following code does not raise a ZeroDivisionError even though b is 0.
python
b = 0
result = b != 0 and 10 / b > 2
print(result)
Solution to Exercise 3
Short-circuit evaluation prevents the division from executing. b != 0 evaluates to False, so and immediately returns False without evaluating the right operand 10 / b > 2.
```python
b = 0
result = b != 0 and 10 / b > 2
print(result) # False
```
and stops at the first falsy operand. Since b != 0 is False, Python never evaluates 10 / b, avoiding the ZeroDivisionError.