Python Conditionals — Language Specification¶
1. Spec Reference¶
- Primary source: Python Language Reference, §8.1 — The
ifstatement https://docs.python.org/3/reference/compound_stmts.html#the-if-statement - Boolean operations: §6.12 — https://docs.python.org/3/reference/expressions.html#boolean-operations
- Comparisons: §6.10 — https://docs.python.org/3/reference/expressions.html#comparisons
matchstatement (structural pattern matching): §8.6 https://docs.python.org/3/reference/compound_stmts.html#the-match-statement- Python 3.12 standard: All rules here apply to CPython 3.12 unless otherwise noted.
2. Formal Grammar (EBNF)¶
2.1 if Statement¶
if_stmt ::= "if" assignment_expression ":" suite
("elif" assignment_expression ":" suite)*
["else" ":" suite]
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
2.2 Conditional Expression (Ternary)¶
conditional_expression ::= or_test ["if" or_test "else" expression]
expression ::= conditional_expression | lambda_expr
2.3 Boolean Operations¶
or_test ::= and_test | or_test "or" and_test
and_test ::= not_test | and_test "and" not_test
not_test ::= comparison | "not" not_test
2.4 Comparisons¶
comparison ::= or_expr (comp_operator or_expr)*
comp_operator ::= "<" | ">" | "==" | ">=" | "<=" | "!="
| "is" ["not"] | ["not"] "in"
2.5 Structural Pattern Matching (Python 3.10+)¶
match_stmt ::= "match" subject_expr ":" NEWLINE INDENT case_block+ DEDENT
case_block ::= "case" patterns [guard] ":" block
patterns ::= open_sequence_pattern | pattern
pattern ::= as_pattern | or_pattern
as_pattern ::= or_pattern "as" capture_pattern
or_pattern ::= "|".closed_pattern+
guard ::= "if" named_expression
2.5.1 Pattern Types¶
capture_pattern ::= !("_") NAME
wildcard_pattern ::= "_"
value_pattern ::= attr ("." attr)*
literal_pattern ::= signed_number | complex_number | strings | "None" | "True" | "False"
group_pattern ::= "(" pattern ")"
sequence_pattern ::= "[" [maybe_sequence_pattern] "]" | "(" [open_sequence_pattern] ")"
mapping_pattern ::= "{" [items_pattern] "}"
class_pattern ::= name_or_attr "(" [pattern_arguments ","?] ")"
3. Core Rules and Constraints¶
3.1 if/elif/else Semantics¶
ifevaluates its condition; if truthy, executes its suite and skips allelif/elsebranches.elifis evaluated only if all precedingif/elifconditions were falsy.elseexecutes only if all preceding conditions were falsy.- Any number of
elifclauses are allowed (including zero). - At most one
elseclause; it must be last.
3.2 Truth Value Testing¶
Per §4.1 of the data model, any object can be tested for truth. An object is falsy if: - It defines __bool__ and that returns False - It defines __len__ and that returns 0 (and __bool__ is not defined)
Objects that are falsy by default: - None - False - Numeric zero: 0, 0.0, 0j, Decimal(0), Fraction(0, 1) - Empty sequences and collections: "", b"", (), [], {}, set(), range(0) - Objects whose __bool__ returns False
Everything else is truthy.
3.3 Short-Circuit Evaluation¶
x and y: evaluatesyonly ifxis truthy; returnsxifxis falsy, otherwise returnsy.x or y: evaluatesyonly ifxis falsy; returnsxifxis truthy, otherwise returnsy.not x: always evaluatesx; returnsTrueifxis falsy,Falseif truthy.- These operators return the actual value of the last-evaluated operand, not necessarily a
bool.
3.4 Comparison Chaining¶
- Comparisons may be chained:
a < b < cis equivalent to(a < b) and (b < c). - Each operand is evaluated exactly once (a key property for expressions with side effects).
- The spec defines this for any number of chained operators.
3.5 Conditional Expression (Ternary Operator)¶
- Syntax:
value_if_true if condition else value_if_false conditionis evaluated first; if truthy,value_if_trueis evaluated and returned; otherwisevalue_if_false.- Only one branch is evaluated (lazy).
3.6 match Statement Rules (PEP 634)¶
matchis a soft keyword: valid as an identifier outside amatchstatement.caseis a soft keyword: valid as an identifier outside acaseblock.- At least one
caseblock is required. - The wildcard pattern
_always matches and never binds. - A
case _:clause functions as a default/fallthrough equivalent. - No fall-through between cases (unlike C
switch). - Capture patterns bind new variables in the enclosing scope.
- OR patterns (
p1 | p2) must bind the same set of variables.
4. Type Rules (Dunder Methods and Protocols)¶
4.1 Truth Value Protocol¶
object.__bool__(self) -> bool
# Called by bool() and conditional expressions.
# If not defined, __len__ is tried.
# If neither defined, the object is always truthy.
object.__len__(self) -> int
# If __bool__ is absent, a non-zero __len__ makes the object truthy.
4.2 Comparison Protocol¶
object.__lt__(self, other) -> bool | NotImplemented
object.__le__(self, other) -> bool | NotImplemented
object.__eq__(self, other) -> bool | NotImplemented
object.__ne__(self, other) -> bool | NotImplemented
object.__gt__(self, other) -> bool | NotImplemented
object.__ge__(self, other) -> bool | NotImplemented
NotImplemented, the reflected method of the right operand is tried. 4.3 in / not in Protocol¶
object.__contains__(self, item) -> bool
# Used by 'item in container'.
# If not defined, Python falls back to iterating over the object.
4.4 Structural Pattern Matching Protocol¶
# Class patterns call __match_args__ to determine positional argument mapping:
cls.__match_args__ = ("attr1", "attr2", ...)
# Example:
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
5. Behavioral Specification¶
5.1 if Statement Execution¶
- Evaluate the expression in the
ifclause. - If truthy, execute the
ifsuite; the statement terminates. - Otherwise, for each
elifclause in order: evaluate its expression; if truthy, execute its suite and terminate. - If no clause executed and an
elseclause exists, execute theelsesuite.
5.2 and / or Return Values¶
and and or return one of their operands, not necessarily True or False:
x = None or "default" # x = "default"
y = "value" or "default" # y = "value"
z = 0 and "unreachable" # z = 0
5.3 Conditional Expression Evaluation Order¶
1. Evaluatecond. 2. If truthy: evaluate and return true_val. 3. If falsy: evaluate and return false_val. 5.4 match Statement Execution (PEP 634)¶
- Evaluate the subject expression once.
- Try each
casepattern in order. - For the first matching pattern: a. Bind any capture variables. b. Evaluate the guard (
ifclause) if present; if falsy, continue to next case. c. Execute the associated block. d. No fall-through to subsequent cases. - If no case matches, no action is taken (no error).
5.5 Pattern Matching Semantics¶
| Pattern | Matches when | Side effects |
|---|---|---|
Literal 42, "x" | Subject equals literal (using ==) | None |
None, True, False | Subject is the singleton | None |
Capture name | Always | Binds name to subject |
Wildcard _ | Always | None |
Class Point(x, y) | isinstance(subj, Point) and attrs match | Binds x, y |
Sequence [a, b] | Subject is a sequence of length 2 | Binds a, b |
Mapping {"key": v} | Subject has key "key" | Binds v |
OR p1 \| p2 | Either pattern matches | Binds same set |
AS p as name | p matches | Binds name to subject |
Star [a, *rest] | Sequence with at least 1 element | Binds a, rest |
6. Defined vs Undefined Behavior¶
6.1 Defined¶
not NoneisTrue— fully defined.0 == FalseisTrue;1 == TrueisTrue(bool subclasses int).- Chained comparisons evaluate each operand exactly once.
and/orshort-circuit: right operand not evaluated if not needed.is/is notnever call__eq__; identity only.None is Noneis alwaysTrue(singleton).
6.2 Undefined / Implementation-Defined¶
- The order in which
or-patterns inmatchare tried is left-to-right (defined by PEP 634). __eq__returningNotImplementedfallback order is defined, but chained==across many custom types can have complex resolution.- Whether a custom
__bool__that returns non-bool is coerced: it is —bool()wraps the result.
7. Edge Cases from the Spec (CPython-Specific Notes)¶
7.1 Empty Containers Are Falsy¶
print(bool([])) # False
print(bool({})) # False
print(bool(set())) # False
print(bool("")) # False
print(bool(0)) # False
print(bool(0.0)) # False
print(bool(None)) # False
7.2 Non-Bool Return from and / or¶
result = [] or {} # result = {} ([] is falsy, returns {})
result = [1] or {} # result = [1] (truthy, returns first truthy)
result = 0 or False or None or "found" # result = "found"
7.3 is vs == with Literals¶
# For None, True, False: 'is' is correct
x = None
if x is None: print("none") # correct
# For other types: use ==
a = "hello"
b = "hello"
print(a is b) # True in CPython (string interning) — NOT reliable
print(a == b) # True — reliable
7.4 Walrus Operator in Conditions (PEP 572)¶
import re
text = "Version: 3.12.0"
if m := re.search(r"\d+\.\d+\.\d+", text):
print(f"Found version: {m.group()}") # Found version: 3.12.0
# Walrus in while loop
data = [1, 2, 3, 4, 5]
while chunk := data[:2]:
print(chunk)
del data[:2]
7.5 match With Guard¶
def classify(point):
match point:
case (x, y) if x == y:
return "diagonal"
case (x, y) if x > 0 and y > 0:
return "first quadrant"
case _:
return "other"
7.6 Truthy numpy Arrays Raise ValueError¶
CPython raises ValueError for numpy.ndarray in boolean context (because __bool__ raises it for multi-element arrays). This is correct per the protocol — __bool__ can raise any exception.
7.7 Chained Comparison With Side Effects¶
count = 0
def check(n):
global count
count += 1
return n
# Each operand evaluated once:
result = check(1) < check(2) < check(3)
print(count) # 3 — each called exactly once
8. Version History (PEPs and Python Versions)¶
| Feature | PEP | Version |
|---|---|---|
if/elif/else | — | Python 1.0 |
x if cond else y conditional expression | PEP 308 | Python 2.5 |
is / is not operators | — | Python 2.1 |
not in operator | — | Python 2.0 |
Walrus operator := in conditions | PEP 572 | Python 3.8 |
Structural pattern matching match/case | PEP 634 | Python 3.10 |
Soft keywords match, case, type | PEP 634, 695 | Python 3.10, 3.12 |
OR patterns p1 \| p2 in match | PEP 634 | Python 3.10 |
__match_args__ protocol | PEP 634 | Python 3.10 |
9. Implementation-Specific Behavior¶
9.1 CPython Short-Circuit Optimization¶
- CPython generates
JUMP_IF_FALSE_OR_POPandJUMP_IF_TRUE_OR_POPbytecodes forand/or. - The right operand is only evaluated if the jump is not taken.
9.2 match Statement Bytecode (CPython 3.10+)¶
matchis compiled to a series of pattern-specific bytecode instructions.- Literal patterns compile to equality checks (
==) except forNone/True/Falsewhich use identity. - Class patterns generate
isinstancechecks followed by attribute loads.
9.3 PyPy¶
- JIT compilation treats
if/elif/elsechains efficiently. - Short-circuit evaluation follows the same semantics.
matchsupport was added alongside CPython 3.10 compatibility.
10. Spec Compliance Checklist¶
-
if/elif/elseuses correct indentation - Truth testing relies on
__bool__/__len__protocol, not explicit== True -
is/is notused only forNone,True,Falseidentity checks -
==used for value equality -
and/orreturn values understood (not necessarilybool) - Walrus operator (
:=) scope understood -
matchpatterns do not fall through between cases - OR patterns in
matchbind the same set of variables -
__match_args__defined for positional class patterns - Guards (
if) incaseclauses correctly used - Chained comparisons evaluate operands exactly once
11. Official Examples (Runnable Python 3.10+)¶
# ----------------------------------------------------------------
# 1. Basic if / elif / else
# ----------------------------------------------------------------
def grade(score: int) -> str:
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
else:
return "F"
print(grade(95)) # A
print(grade(73)) # C
print(grade(45)) # F
# ----------------------------------------------------------------
# 2. Truth value testing
# ----------------------------------------------------------------
values = [0, "", [], None, False, 0.0, set(), {}, "hello", [1], True]
for v in values:
print(f"{repr(v):15} -> {bool(v)}")
# ----------------------------------------------------------------
# 3. Short-circuit evaluation
# ----------------------------------------------------------------
def side_effect(name, val):
print(f" evaluating {name}")
return val
print("and short-circuit:")
result = side_effect("left", False) and side_effect("right", True)
# Only "left" is printed; right is NOT evaluated
print("or short-circuit:")
result = side_effect("left", True) or side_effect("right", False)
# Only "left" is printed
# ----------------------------------------------------------------
# 4. and/or return values (not necessarily bool)
# ----------------------------------------------------------------
x = None
y = x or "default"
print(y) # "default"
name = "Alice"
greeting = name and f"Hello, {name}!"
print(greeting) # "Hello, Alice!"
empty = []
result = empty or [1, 2, 3]
print(result) # [1, 2, 3]
# ----------------------------------------------------------------
# 5. Conditional expression (ternary)
# ----------------------------------------------------------------
n = 7
label = "odd" if n % 2 != 0 else "even"
print(label) # odd
# Nested ternary (works but prefer if/elif for readability)
category = "low" if n < 5 else ("high" if n > 10 else "medium")
print(category) # medium
# ----------------------------------------------------------------
# 6. Chained comparisons
# ----------------------------------------------------------------
x = 5
print(1 < x < 10) # True
print(0 <= x <= 5) # True
print(x == 5 == 5) # True
# Each middle operand evaluated once:
import random
# 0 <= random.randint(0,10) <= 10 (randint called once)
# ----------------------------------------------------------------
# 7. Walrus operator in conditions (PEP 572)
# ----------------------------------------------------------------
import re
text = "order_id: 12345"
if (m := re.search(r"\d+", text)) is not None:
print(f"Found ID: {m.group()}") # Found ID: 12345
# ----------------------------------------------------------------
# 8. Structural pattern matching (PEP 634, Python 3.10+)
# ----------------------------------------------------------------
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
def describe_point(point):
match point:
case Point(0, 0):
return "Origin"
case Point(x, 0):
return f"On x-axis at {x}"
case Point(0, y):
return f"On y-axis at {y}"
case Point(x, y) if x == y:
return f"On diagonal at {x}"
case Point(x, y):
return f"Point({x}, {y})"
case _:
return "Not a point"
print(describe_point(Point(0, 0))) # Origin
print(describe_point(Point(3, 0))) # On x-axis at 3
print(describe_point(Point(4, 4))) # On diagonal at 4
print(describe_point(Point(1, 2))) # Point(1, 2)
# ----------------------------------------------------------------
# 9. match with sequence patterns
# ----------------------------------------------------------------
def http_action(request):
match request:
case {"method": "GET", "url": url}:
return f"GET {url}"
case {"method": "POST", "url": url, "body": body}:
return f"POST {url} with body"
case {"method": method}:
return f"Unknown method: {method}"
case _:
return "Invalid request"
print(http_action({"method": "GET", "url": "/home"}))
print(http_action({"method": "DELETE", "url": "/res"}))
# ----------------------------------------------------------------
# 10. match with OR patterns
# ----------------------------------------------------------------
def classify_status(code: int) -> str:
match code:
case 200 | 201 | 202:
return "success"
case 301 | 302:
return "redirect"
case 400 | 401 | 403 | 404:
return "client error"
case 500 | 502 | 503:
return "server error"
case _:
return "unknown"
print(classify_status(201)) # success
print(classify_status(404)) # client error
print(classify_status(503)) # server error
12. Related Spec Sections¶
| Section | Topic | URL |
|---|---|---|
| §6.10 | Comparisons | https://docs.python.org/3/reference/expressions.html#comparisons |
| §6.11 | Boolean operations | https://docs.python.org/3/reference/expressions.html#boolean-operations |
| §6.12 | Conditional expressions | https://docs.python.org/3/reference/expressions.html#conditional-expressions |
| §8.1 | The if statement | https://docs.python.org/3/reference/compound_stmts.html#the-if-statement |
| §8.6 | The match statement | https://docs.python.org/3/reference/compound_stmts.html#the-match-statement |
| §3.3.1 | Truth value testing | https://docs.python.org/3/reference/datamodel.html#truth-value-testing |
| §4.2.2 | Name resolution (scoping) | https://docs.python.org/3/reference/executionmodel.html#resolution-of-names |
| PEP 308 | Conditional expression | https://peps.python.org/pep-0308/ |
| PEP 572 | Walrus operator | https://peps.python.org/pep-0572/ |
| PEP 634 | Pattern matching | https://peps.python.org/pep-0634/ |
| PEP 635 | Pattern matching motivation | https://peps.python.org/pep-0635/ |
| PEP 636 | Pattern matching tutorial | https://peps.python.org/pep-0636/ |