Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Séminaire 2 : Bases de Python I — Structures de contrôle


Objectifs d’apprentissage

À la fin de ce séminaire vous serez capable de :

  • Comprendre ce qu’est le flux de contrôle et pourquoi les algorithmes en dépendent

  • Écrire des branches if/elif/else et raisonner sur les tables de vérité

  • Utiliser des boucles for avec range(), enumerate() et zip()

  • Utiliser des boucles while en toute sécurité, et savoir quand les préférer aux for

  • Contrôler l’exécution des boucles avec break, continue et pass

  • Reconnaître la complexité des boucles imbriquées (informellement : O(n²))

  • Écrire des list comprehensions basiques


Partie 1 : Théorie

2.1 Qu’est-ce que le flux de contrôle ?

Par défaut, Python exécute les instructions de haut en bas, les unes après les autres. Le flux de contrôle modifie cet ordre par défaut : il permet au programme de prendre des décisions, de répéter des actions et d’ignorer du code.

Tout algorithme se réduit finalement à :

  1. Séquence — faire A, puis B, puis C

  2. Sélection — si la condition X est vraie, faire A ; sinon faire B

  3. Itération — répéter A jusqu’à ce que la condition Y soit satisfaite

Ces trois constructions suffisent pour exprimer n’importe quelle fonction calculable (c’est ce qu’on appelle la complétude de Turing).

Pourquoi est-ce important pour les algorithmes ?

La structure de votre flux de contrôle détermine directement :

  • Correction — l’algorithme produit-il la bonne réponse ?

  • Efficacité — combien d’opérations effectue-t-il ?

Par exemple, la recherche linéaire parcourt chaque élément (une boucle), tandis que la recherche binaire divise l’espace de recherche par deux à chaque étape (boucle + branchement). Même problème, performances radicalement différentes.

Control Flow at a Glance
─────────────────────────────────────────────────
Sequence:     A → B → C
Selection:    condition? → A (yes) / B (no)
Iteration:    condition? → A → back to condition
─────────────────────────────────────────────────

2.2 if / elif / else — Branching

Syntaxe

if condition_1:
    # executed when condition_1 is True
elif condition_2:
    # executed when condition_1 is False AND condition_2 is True
else:
    # executed when ALL conditions above are False
  • Les clauses elif et else sont optionnelles.

  • Vous pouvez avoir plusieurs clauses elif.

  • Les conditions sont toute expression qui évalue à une valeur truthy ou falsy.

Truthiness en Python

Falsy (traité comme False)Truthy (traité comme True)
False, NoneTrue
0, 0.0Tout nombre non nul
"", ''Toute chaîne non vide
[], (), {}, set()Tout conteneur non vide

Opérateurs de comparaison

OpérateurSignification
==Égal à
!=Différent de
<, >Inférieur / supérieur à
<=, >=Inférieur / supérieur ou égal à
isIdentité (même objet en mémoire)
inTest d’appartenance

Opérateurs booléens : table de vérité

ABA and BA or Bnot A
TTTTF
TFFTF
FTFTT
FFFFT
# --- Basic if/elif/else ---

def classify_grade(score):
    """Return a letter grade for a score in [0, 100]."""
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    else:
        return "F"


scores = [95, 83, 71, 64, 50, 100, 0]
for s in scores:
    print(f"Score {s:>3} → Grade {classify_grade(s)}")

print()

# --- Chained comparisons (Python-specific, very readable) ---
x = 15
if 10 <= x <= 20:   # equivalent to: 10 <= x and x <= 20
    print(f"{x} is between 10 and 20 (inclusive)")

# --- Ternary / conditional expression (one-liner if/else) ---
temperature = 22
weather = "warm" if temperature >= 20 else "cold"
print(f"Temperature {temperature}°C is {weather}")
# --- Interactive demo: which branch executes? ---
import ipywidgets as widgets
from IPython.display import display, HTML


def show_branch(number):
    """Show which if/elif/else branch is taken for a given number."""
    output_lines = [f"<b>Input number: {number}</b><br>"]
    output_lines.append("<pre style='font-family:monospace; font-size:13px;'>")

    # We manually trace through each condition
    if number > 0:
        branch = "POSITIVE"
        colour = "#2ecc71"
    elif number < 0:
        branch = "NEGATIVE"
        colour = "#e74c3c"
    else:
        branch = "ZERO"
        colour = "#3498db"

    # Build a visual "code trace"
    conditions = [
        (f"if {number} > 0:",     number > 0),
        (f"elif {number} < 0:",   number < 0),
        ("else:",                 not (number > 0 or number < 0)),
    ]
    for code_line, taken in conditions:
        marker = " ← TAKEN" if taken else ""
        style = f"color:{colour}; font-weight:bold;" if taken else "color:#888;"
        output_lines.append(
            f"<span style='{style}'>{code_line}{marker}</span><br>"
        )

    output_lines.append("</pre>")
    output_lines.append(
        f"<p style='color:{colour}; font-size:15px;'>Result: <b>{branch}</b></p>"
    )
    display(HTML("".join(output_lines)))


slider = widgets.IntSlider(
    value=0, min=-20, max=20, step=1,
    description="Number:",
    style={"description_width": "initial"},
    layout=widgets.Layout(width="55%")
)

out = widgets.interactive_output(show_branch, {"number": slider})
display(widgets.Label("Move the slider and watch which branch is taken:"), slider, out)

2.3 for Loops — Itération définie

Une boucle for itère sur n’importe quel itérable (liste, chaîne, range, dict, fichier, …). Vous savez à l’avance (conceptuellement) combien d’itérations auront lieu.

range()

AppelValeurs produites
range(5)0, 1, 2, 3, 4
range(2, 7)2, 3, 4, 5, 6
range(0, 10, 2)0, 2, 4, 6, 8
range(5, 0, -1)5, 4, 3, 2, 1

enumerate() — boucle avec indice

Utilisez enumerate() au lieu de range(len(...)) — c’est plus Pythonique et moins sujet aux erreurs.

# BAD (C-style, avoid in Python):
for i in range(len(items)):
    print(items[i])

# GOOD:
for i, item in enumerate(items):
    print(i, item)

zip() — itérer sur plusieurs itérables

names = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
    print(f"{name}: {score}")
# --- range() examples ---
print("range(5):",        list(range(5)))
print("range(2, 7):",     list(range(2, 7)))
print("range(0,10,2):",   list(range(0, 10, 2)))
print("range(5,0,-1):",   list(range(5, 0, -1)))

print()

# --- Iterating over a list ---
fruits = ["apple", "banana", "cherry", "date"]

print("Direct iteration:")
for fruit in fruits:
    print(f"  {fruit}")

print()

# --- enumerate() ---
print("With enumerate():")
for index, fruit in enumerate(fruits):
    print(f"  [{index}] {fruit}")

# enumerate() with a start index
print("\nStarting from index 1:")
for rank, fruit in enumerate(fruits, start=1):
    print(f"  #{rank}: {fruit}")

print()

# --- zip() ---
names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 72]
grades = ["A", "B", "C"]

print("With zip():")
for name, score, grade in zip(names, scores, grades):
    print(f"  {name:>6}: {score} ({grade})")

print()

# --- Iterating over a string (strings are iterables!) ---
word = "Python"
print(f"Letters in '{word}': ", end="")
for ch in word:
    print(ch, end=" ")
print()

# --- Iterating over a dict ---
capitals = {"France": "Paris", "Japan": "Tokyo", "Brazil": "Brasília"}
print("\nCountry capitals:")
for country, capital in capitals.items():
    print(f"  {country}: {capital}")

2.4 while Loops — Itération indéfinie

Utilisez une boucle while lorsque vous ne savez pas à l’avance combien d’itérations sont nécessaires — seule la condition d’arrêt importe.

while condition:
    # body — runs as long as condition is True

Quand choisir while plutôt que for

SituationÀ utiliser
Nombre connu d’itérationsfor
Itérer sur une collectionfor
Attente d’une entrée utilisateur / événementwhile
Algorithme qui converge (ex. méthode de Newton)while
Boucle de jeuwhile

Danger : boucles infinies

Si la condition ne devient jamais False, la boucle tourne indéfiniment. Assurez-vous toujours que :

  1. La variable de boucle est mise à jour à l’intérieur du corps.

  2. La condition sera finalement satisfaite.

  3. Vous avez un break comme sortie d’urgence si nécessaire.

import math

# --- Basic while loop ---
counter = 0
while counter < 5:
    print(f"  counter = {counter}")
    counter += 1   # IMPORTANT: increment to avoid infinite loop
print("Loop finished.")

print()

# --- Collatz conjecture: a famous sequence ---
# Start with any positive integer n.
# If n is even: divide by 2. If odd: multiply by 3 and add 1.
# Conjecture: it always reaches 1 (unproven for all numbers!).

def collatz(n):
    """Return the Collatz sequence starting at n."""
    sequence = [n]
    while n != 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        sequence.append(n)
    return sequence


for start in [6, 11, 27]:
    seq = collatz(start)
    print(f"Collatz({start}): {seq}")
    print(f"  Steps to reach 1: {len(seq) - 1}")

print()

# --- Newton's method: square root approximation ---
# Uses while loop because convergence time is not known in advance.

def sqrt_newton(n, tolerance=1e-10):
    """Approximate sqrt(n) using Newton-Raphson iteration."""
    if n < 0:
        raise ValueError("Cannot take square root of a negative number")
    guess = n / 2.0       # initial guess
    iterations = 0
    while abs(guess * guess - n) > tolerance:
        guess = (guess + n / guess) / 2  # Newton step
        iterations += 1
    return guess, iterations



for num in [2, 9, 100, 12345]:
    approx, iters = sqrt_newton(num)
    print(f"sqrt({num:>5}): approx={approx:.8f}, math.sqrt={math.sqrt(num):.8f}, iters={iters}")

2.5 break, continue, pass

InstructionEffet
breakSort immédiatement de la boucle la plus interne
continueSaute le reste de l’itération courante ; passe à la suivante
passNe fait rien — un placeholder syntaxique (bloc vide)

Clause else sur les boucles

Python a une caractéristique particulière : les boucles for/while peuvent avoir une clause else qui s’exécute seulement si la boucle s’est terminée normalement (c’est-à-dire qu’elle n’a pas été interrompue par un break).

for item in collection:
    if condition:
        break      # skip the else
else:
    # runs only if break was never hit
    print("No item matched")

Ceci est très utile pour les algorithmes de recherche.

# --- break: stop the loop early ---
print("=== break ===")
for i in range(10):
    if i == 5:
        print(f"  Found 5! Breaking out of loop.")
        break
    print(f"  i = {i}")

print()

# --- continue: skip even numbers ---
print("=== continue (printing only odd numbers) ===")
for i in range(10):
    if i % 2 == 0:
        continue    # skip even
    print(f"  {i}")

print()

# --- pass: placeholder in an empty branch ---
print("=== pass ===")
for i in range(5):
    if i == 2:
        pass   # placeholder — do nothing for 2 (no error without a body)
    else:
        print(f"  Processing {i}")

print()

# --- for/else: prime checking ---
print("=== for...else: prime checking ===")

def is_prime(n):
    """Return True if n is prime, False otherwise."""
    if n < 2:
        return False
    for divisor in range(2, int(n ** 0.5) + 1):
        if n % divisor == 0:
            return False  # found a divisor — not prime
    return True            # loop completed without break → prime


primes = [n for n in range(2, 30) if is_prime(n)]
print(f"  Primes below 30: {primes}")

print()

# Version explicitly using for/else:
def is_prime_explicit(n):
    """Same as above but uses for...else explicitly to illustrate the pattern."""
    if n < 2:
        return False
    for divisor in range(2, int(n ** 0.5) + 1):
        if n % divisor == 0:
            break       # composite — break skips the else
    else:
        return True     # else only runs if no break occurred
    return False


test_nums = [2, 7, 9, 13, 25, 29]
for num in test_nums:
    print(f"  is_prime({num}) = {is_prime_explicit(num)}")

2.6 Boucles imbriquées et complexité

Une boucle à l’intérieur d’une autre boucle s’appelle une boucle imbriquée. Elles sont nécessaires pour travailler avec des données 2D (matrices, grilles) ou pour certains algorithmes (tri à bulles, multiplication de matrices).

Informellement : O(n²)

Si la boucle externe et la boucle interne exécutent chacune n itérations, le nombre total d’opérations est approximativement n × n = n². On l’écrit O(n²) (notation Big O — vue formellement plus tard dans le cours).

nn (boucle simple)n² (boucle imbriquée)
1010100
10010010 000
1 0001 0001 000 000
10 00010 000100 000 000

C’est pourquoi des algorithmes comme tri à bulles (O(n²)) deviennent peu pratiques pour de grandes entrées, tandis que tri fusion (O(n log n)) s’adapte bien mieux.

# --- Nested loop: 5×5 multiplication table ---
print("5 × 5 Multiplication Table:")
print("   ", end="")
for j in range(1, 6):
    print(f"{j:>4}", end="")
print()
print("   " + "-" * 20)

for i in range(1, 6):          # outer loop: rows
    print(f"{i:>2} |", end="")
    for j in range(1, 6):      # inner loop: columns
        print(f"{i * j:>4}", end="")
    print()   # newline after each row

print()

# --- Count operations to visualise O(n²) growth ---
import matplotlib.pyplot as plt

ns = list(range(1, 51))
ops_linear = ns                          # O(n)
ops_quadratic = [n ** 2 for n in ns]     # O(n²)
ops_nlogn = [n * (n.bit_length()) for n in ns]  # rough O(n log n)

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(ns, ops_linear,    label="O(n)      — linear",    linewidth=2)
ax.plot(ns, ops_nlogn,     label="O(n log n) — merge sort", linewidth=2, linestyle="--")
ax.plot(ns, ops_quadratic, label="O(n²)     — nested loop", linewidth=2, linestyle=":")
ax.set_xlabel("Input size n")
ax.set_ylabel("Number of operations (approx.)")
ax.set_title("Growth of Algorithm Complexity", fontweight="bold")
ax.legend()
ax.set_xlim(1, 50)
ax.set_ylim(0)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.tight_layout()
plt.show()

2.7 List Comprehensions — Itération concise

Une list comprehension crée une nouvelle liste en appliquant une expression à chaque élément d’un itérable, avec un filtrage optionnel :

[expression  for variable in iterable  if condition]

Équivalente à une boucle for avec append(), mais plus concise et souvent plus rapide.

# Classic loop approach:
squares = []
for x in range(10):
    squares.append(x ** 2)

# List comprehension (equivalent, preferred in Python):
squares = [x ** 2 for x in range(10)]

Règle empirique : si la comprehension tient sur une seule ligne et reste lisible, utilisez-la. Si elle nécessite plusieurs lignes ou une logique complexe, utilisez une boucle classique.

# --- Basic list comprehension ---
squares = [x ** 2 for x in range(1, 11)]
print(f"Squares 1..10: {squares}")

# --- With condition (filter) ---
even_squares = [x ** 2 for x in range(1, 11) if x % 2 == 0]
print(f"Even squares:  {even_squares}")

# --- Transforming strings ---
words = ["hello", "world", "python", "algorithmics"]
upper_long = [w.upper() for w in words if len(w) > 5]
print(f"Uppercased long words: {upper_long}")

# --- Flattening a 2D list ---
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [elem for row in matrix for elem in row]
print(f"Flattened matrix: {flat}")

# --- Dictionary comprehension (bonus) ---
word_lengths = {word: len(word) for word in words}
print(f"Word lengths: {word_lengths}")

# --- Set comprehension (bonus) ---
unique_lengths = {len(word) for word in words}
print(f"Unique lengths: {unique_lengths}")

# --- Performance note ---
# Comprehensions are generally faster than equivalent loops because
# the iteration is optimised at the C level in CPython.
import timeit

loop_time = timeit.timeit(
    "result = []\nfor x in range(1000):\n    result.append(x**2)",
    number=10000
)
comp_time = timeit.timeit(
    "result = [x**2 for x in range(1000)]",
    number=10000
)

print(f"\nPerformance (10000 runs of squaring 1000 elements):")
print(f"  for loop:          {loop_time:.4f}s")
print(f"  list comprehension:{comp_time:.4f}s")
print(f"  Speedup: {loop_time / comp_time:.2f}x")

Partie 2 : Exercices


Exercice 1 : Maximum de trois nombres

Écrivez une fonction max_of_three(a, b, c) qui retourne le plus grand des trois nombres en utilisant if/elif/else (n’utilisez pas la fonction intégrée max()).

Ceci est l’équivalent Python de l’exercice C largest_num.c.

# Exercise 1 — Maximum of three numbers
# Your code goes here

Exercice 2 : FizzBuzz

Question classique d’entretien. Affichez les nombres de 1 à n, mais :

  • Affichez “Fizz” pour les multiples de 3

  • Affichez “Buzz” pour les multiples de 5

  • Affichez “FizzBuzz” pour les multiples à la fois de 3 et de 5

  • Affichez le nombre lui-même sinon

Important : vérifiez d’abord la condition combinée (FizzBuzz), sinon elle ne sera jamais atteinte.

# Exercise 2 — FizzBuzz
# Your code goes here

Exercice 3 : Vérificateur d’année bissextile

Une année est bissextile si :

  • Elle est divisible par 4 ET

  • Elle n’est pas divisible par 100, sauf si elle est aussi divisible par 400.

Exemples : 2000 ✓, 1900 ✗, 2024 ✓, 2023 ✗

Écrivez is_leap_year(year) et testez-la.

# Exercise 3 — Leap year checker

Exercice 4 : Calculatrice interactive (boucle while)

Créez une calculatrice qui demande de manière répétée à l’utilisateur deux nombres et un opérateur, puis affiche le résultat. La boucle continue jusqu’à ce que l’utilisateur tape quit.

Opérations supportées : +, -, *, /

# Exercise 4 — Interactive calculator with while loop

Exercice 5 : Table de multiplication

Écrivez une fonction multiplication_table(n) qui affiche la table de multiplication pour un nombre donné n (de 1 × n à 12 × n).

Extension : affichez une grille complète n × n.

# Exercise 5 — Multiplication table

Récapitulatif

ConceptQuand utiliser
if/elif/elsePrise de décision basée sur des conditions
forNombre d’itérations connu / itérer sur une collection
whileNombre d’itérations inconnu, piloté par événements, convergence
breakSortir prématurément d’une boucle (recherche trouvée / erreur)
continueIgnorer l’élément courant, continuer avec le suivant
passPlaceholder syntaxique pour blocs vides
List comprehensionCréation concise de listes à partir d’une itération

Prochain séminaire : Structures de données — listes, tuples, dictionnaires, ensembles, fonctions et classes.