Python Notes : More Control Flow Tools (Statements)
Unpacking
use two operators * (for tuples) and ** (for dictionaries)
* is called a splat
can't pass a list to a function expecting a tuple
# A sample function that takes 4 arguments
# and prints them.
def fun(a, b, c, d):
    print(a, b, c, d)

# Driver Code
my_list = [1, 2, 3, 4]

# This doesn't work
fun(my_list)
unpacking the list used as an argument provides the function's expected arguments
# Unpacking list into four arguments
fun(*my_list)
if Statements
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...    x = 0
...    print('Negative changed to zero')
... elif x == 0:
...    print('Zero')
...elif x == 1:
...    print('Single')
...else:
...    print('More')
...
More
elif is an abbreviation of 'else if''
an if ... elif ... elif ... sequence is a substitute for a switch
for Statements
Python's for statement iterates over the items of any sequence in the order that they appear in the sequence
>>> # Measure some strings:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...    print(w, len(w))
...
cat 3
window 6
defenestrate 12
code that modifies a collection while iterating over that same collection can be tricky to get right
usually more straight-forward to loop over a copy of the collection or to create a new collection
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}

# Strategy:  Iterate over a copy
for user, status in users.copy().items():
    if status == 'inactive':
        del users[user]

# Strategy:  Create a new collection
active_users = {}
for user, status in users.items():
    if status == 'active':
        active_users[user] = status
range() Function
range() function generates arithmetic progressions
>>> for i in range(5):
...    print(i)
...
0
1
2
3
4
object returned by range() is not a list
iterable object
>>> sum(range(4))  # 0 + 1 + 2 + 3
6
break and continue Statements
the break statement breaks out of the innermost enclosing for or while loop
>>> for n in range(2, 10):
    for x in range(2, n):
...       if n % x == 0:
...           print(f"{n} equals {x} * {n//x}")
...           break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3
the continue statement continues with the next iteration of the loop
>>> for num in range(2, 10):
...    if num % 2 == 0:
...        print(f"Found an even number {num}")
...        continue
...    print(f"Found an odd number {num}")
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
else Clauses on Loops
a for or while loop the break statement may be paired with an else clause
if the loop finishes without executing the break, the else clause executes
>>> for n in range(2, 10):
...    for x in range(2, n):
...        if n % x == 0:
...            print(n, 'equals', x, '*', n//x)
...            break
...    else:
...        # loop fell through without finding a factor
...        print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
pass Statements
does nothing
an be used when a statement is required syntactically but the program requires no action
>>> while True:
...    pass  # Busy-wait for keyboard interrupt (Ctrl+C)
...
use to create minimal classes
>>> class MyEmptyClass:
...    pass
...
use as a placeholder for a function or conditional body when working on new code
>>> def initlog(*args):
... pass
...
match Statements
a match statement takes an expression and compares its value to successive patterns given as one or more case blocks
def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"
underscore case is the default
can combine literals in a single pattern
case 401 | 403 | 404:
    return "Not allowed"
patterns can look like unpacking assignments and can be used to bind variables
first pattern uses two literals
last pattern uses two variables
# point is an (x, y) tuple
match point:
    # two literals
    case (0, 0): 
        print("Origin")
    # literal and variable
    case (0, y):
        print(f"Y={y}")
    # variable and literal
    case (x, 0):
        print(f"X={x}")
    # two variables
    case (x, y): 
        # conceptually similar to the unpacking assignment : (x, y) = point
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")
use class psuedo-constructor to capture attributes into variables
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def where_is(point):
    match point:
        case Point(x=0, y=0):
            print("Origin")
        case Point(x=0, y=y):
            print(f"Y={y}")
        case Point(x=x, y=0):
            print(f"X={x}")
        case Point():
            print("Somewhere else")
        case _:
            print("Not a point")
examples covering all possible matches
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
can add a conditional guard to a pattern
if the conditional fails flow continues to the next pattern
match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")
tuple and list patterns do not match iterators or strings
sequence patterns support extended unpacking of lists and tuples
[x, y, *rest] and (x, y, *rest) work similar to unpacking assignments
the name after * may also be _, so (x, y, *_) matches a sequence of at least two items without binding the remaining items
assumption : *rest is a container binding the remaining elements

mapping patterns used with values from a dictionary
{"bandwidth": b, "latency": l} captures the "bandwidth" and "latency" values from a dictionary
extra keys are ignored unlike with sequence patterns
assumption : previous assumption appears to be correct
unpacking like *rest is also supported
{"bandwidth": b, **rest} captures the "bandwidth" value and the remaining key-value pairs into a dictionary

subpatterns can be captured using the as keyword

case (Point(x1, y1), Point(x2, y2) as p2): ...
most literals are compared by equality
singletons like None, True, and False are compared by identity

patterns may use named constants
these must be dotted names to prevent them from being interpreted as capture variable

from enum import Enum
class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

match color:
    case Color.RED:
        print("I see red!")
    case Color.GREEN:
        print("Grass is green")
    case Color.BLUE:
        print("I'm feeling the blues :(")
previous    index    next