Control Flows
Contents
1.4.2. Control Flows#
Python usual control flows of traditional language, with light variation.
In this section, we introduce the control flows used by Python for conditions, looping, and functions.
1.4.2.1. if
Statements#
The if
statement allow execution of a code section when a condition is met.
from random import randint
x: int = randint(-10, 10)
if x < 0:
print(f"{x} is a negative number.")
elif x % 2 == 0:
print(f"{x} is even.")
else:
print(f"{x} is odds.")
-10 is a negative number.
When a value is compared to several constants, the match
statements might be more appropriate for comparison.
1.4.2.2. match
Statements#
The match
statement compares a value with consecutive constant. Upon the first case matching, the case’s code branch is executed.
Note: the variable name _
act as a wildcard and can always be executed. If no patterns match, none of the branches are executed.
import math
from random import randint
point: tuple[int, int] = (randint(-2, 2), randint(-2, 2))
print(f"{point=}")
match point:
case (0, 0):
print("Distance from origin: 0")
case (0, x) | (x, 0):
print(f"Distance from origin: {x}")
case (x, y):
print(f"Distance from origin: {math.dist((x, y), (0, 0))}")
case _:
raise ValueError("Not a point")
point=(1, 2)
Distance from origin: 2.23606797749979
1.4.2.3. while
and for
Statements#
The while
statement iterates as long as its condition evaluation is truthy.
# Fibonacci series
# The sum of two elements defines the next
a: int = 0
b: int = 1
while a < 10:
print(a)
a, b = b, a+b
0
1
1
2
3
5
8
The for
statement iterates over a sequence of elements in the order they appear.
words: list[str] = ["banana", "apple", "kiwi"]
for word in words:
print(word, len(word))
banana 6
apple 5
kiwi 4
1.4.2.3.1. The range()
function#
To iterate over a sequence of numbers, the range
functions is useful.
for n in range(5):
print(n)
0
1
2
3
4
1.4.2.3.2. break
and continue
Statements, and else
Clauses on Loops#
The break
statement, breaks out the innermost for
or while
loop.
The else
clause on loops executes when the sequence is exhausted (with for
) or the condition becomes false (with while
), but not when a break
statement terminates the loop.
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, "equals", x, "*", n//x)
break
else:
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
The continue
statement goes directly to the next iteration of the loop.
for n in range(2, 10):
if n % 2 == 0:
print(f"{n} is an even number.")
continue
print(f"{n} is an odd number.")
2 is an even number.
3 is an odd number.
4 is an even number.
5 is an odd number.
6 is an even number.
7 is an odd number.
8 is an even number.
9 is an odd number.
1.4.2.4. pass
Statements#
The pass
statement does nothing. It can be used when the syntax requires a statement, but no action is required.
This is often used to implement minimal classes.
class TargetNotFound(Exception):
pass
1.4.2.5. Defining Functions with def
Statements#
A Python function is defined with a name, and optionally parameters and return statements.
Note: When the return statement is omitted, the None
value is returned.
def fib_print(n: int) -> None:
"""Print the Fibonacci series up to `n`.
Parameters
----------
n : int
Maximum value for the Fibonacci series.
"""
a: int = 0
b: int = 1
while a < n:
print(a, end=" ")
a, b = b, a+b
print()
fib_print(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
def fib(n: int) -> list[int]:
"""Return the Fibonacci series up to `n`.
Parameters
----------
n : int
Maximum value for the Fibonacci series.
Returns
-------
series: list[int]
Fibonacci series.
"""
series: list[int] = []
a: int = 0
b: int = 1
while a < n:
series.append(a)
a, b = b, a+b
return series
fib(2000)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
1.4.2.5.1. Unpacking arguments#
Sequence can be unpacked into multiple variable. This can come in handy to:
Pass a list of arguments to a function
Assign function return values to multiple variables
import math
from random import uniform
def rotation(x: float, y: float, r: float) -> tuple[float, float]:
"""Rotate a pair of coordinate in Euclidean space by a `r` factor.
Parameters
----------
x : float
x coordinate.
y : float
y coordinate.
r : float
Returns
-------
(x_, y_) : tuple[float, float]
Rotated coordinates.
"""
x_ = x*math.cos(r) - y*math.sin(r)
y_ = y*math.sin(r) + x*math.cos(r)
return x_, y_
point: tuple[float, float] = (uniform(-1, 1), uniform(-1, 1))
print(f"Original: {point=}")
# Unpack the value of point with `*`.
# Then the function return values are unpacked into x and y respectively.
x_, y_ = rotation(*point, r=90)
point_: tuple[float, float] = (x_, y_)
print(f"Rotated: {point_=}")
Original: point=(0.2993129412057509, -0.2851203460877807)
Rotated: point_=(0.12078240620679381, -0.38901087004743085)
1.4.2.5.2. More on function parameters#
A function can define its parameters with default values or to be either:
Positional only
Positional or keyword
Keyword only
Example:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
As guidance:
Use positional-only if you want the name of the parameters to not be available to the user. This is useful when parameter names have no real meaning, if you want to enforce the order of the arguments when the function is called or if you need to take some positional parameters and arbitrary keywords.
Use keyword-only when names have meaning and the function definition is more understandable by being explicit with names or you want to prevent users relying on the position of the argument being passed.
For an API, use positional-only to prevent breaking API changes if the parameter’s name is modified in the future.
from random import randint
from typing import TypeVar
T = TypeVar("T")
def sample(
l: list[T],
/,
n: int,
*,
resample: bool = False,
) -> list[T]:
"""Sample `n` elements from a list.
Notes
-----
When `resample` is False and the element to sample is greater than the number of element in the list, the values of the list are returned in random order. That is, we cannot sample more elements that the list has.
Parameters
----------
l : list[T]
List of elements to sample from.
n : int
Number of elements to sample.
resample : bool, optional
Whether resampling is allowed, by default False.
Returns
-------
rv: list[T]
Sample of elements.
"""
rv: list[T] = []
l_: list[T] = l.copy()
# Limit max samples when resampling is disabled.
if not resample:
n = min(len(l_), n)
for _ in range(n):
index: int = randint(0, len(l_) - 1)
rv.append(l_[index])
if not resample:
del l_[index]
return rv
arr: list = [1, 2, 3, 4, 5]
sample(arr, 7, resample=False)
[5, 3, 1, 2, 4]
1.4.2.5.3. Arbitrary arguments#
A function can define a parameter *args
wrapping subsequent arguments in a tuple
. Parameters occurring after the *args
parameter must be keyword-only arguments.
Similarly, a function can define a keyword-only parameter **kwargs
wrapping subsequent arguments in a dict
. Not other parameters can follow the **kwargs
parameter.
def concat(*args: tuple[str], sep="/") -> str:
"""Concatenate a sequence of string.
Parameters
----------
sep : str, optional
Separator to use when concatenating, by default "/"
Returns
-------
str
Concatenated string.
"""
return sep.join(args)
path: list[str] = ["/usr", "bin", "python"]
print(concat(*path))
/usr/bin/python
1.4.2.5.4. lambda
Statements#
Anonymous functions can be declared using the lambda
keyword. They are restricted to a single expression.
fma = lambda a, b, c: a*b + c
print(fma(1, 2, 3))
5
It is often used to pass a one-time use function as an argument.
# Sort list of point by distance to origin
import math
points: list[tuple[int, int]] = [(1, 1), (-1, 3), (3, 1), (2, 1)]
points.sort(
key=lambda point: math.dist(point, (0,)*len(point))
)
print(points)
[(1, 1), (2, 1), (-1, 3), (3, 1)]