PYTHON → ROBOTIQUE · 09

Séance 09 · Partie A · Python pas à pas

Modules et
bibliothèques.

Personne ne réécrit la racine carrée ou le générateur aléatoire. Python livre des centaines de modules prêts à l’emploi, et vous allez apprendre à créer les vôtres.

Durée : 90 min · Objectifs : import sous toutes ses formes, tour d’horizon de math, random, time, statistics, collections, itertools ; écrire et importer son propre module ; installer une bibliothèque externe.

Ce que vous saurez faire à la fin
  • Trouver et utiliser la fonction qu’il vous faut dans la bibliothèque standard.
  • Découper un projet en plusieurs fichiers.
  • Comprendre if __name__ == "__main__":.
  • Installer un paquet avec pip sur votre PC.

01 / import

Quatre façons d’importer

Laquelle choisir ?

La forme 1 est la plus claire : en lisant math.sqrt, on sait d’où vient la fonction. La forme 2 est bien pour deux ou trois noms très utilisés. L’alias (3) est la convention pour certaines bibliothèques (import numpy as np). La forme 4 déverse des dizaines de noms dans votre programme et peut en écraser d’autres silencieusement : on ne l’utilise pas. dir(module) liste ce qu’il contient ; help(module.fonction) explique.

02 / La bibliothèque standard

math : ce que la calculatrice sait faire, et plus

Radians

Toutes les fonctions trigonométriques travaillent en radians : 360° = 2π rad. Oublier la conversion est l’erreur n°1 des programmes de robots qui tournent dans le mauvais sens. atan2(y, x) donne l’angle du vecteur (x, y) dans le bon quadrant, contrairement à atan(y/x). La séance 14 y consacre une heure entière.

02 / La bibliothèque standard

random : simuler le hasard

Pseudo-aléatoire

Un ordinateur ne sait pas tirer au hasard : il calcule une suite déterministe qui en a l’air, à partir d’une graine. Fixer la graine avec seed permet de reproduire exactement une simulation (indispensable pour déboguer). gauss(moyenne, écart-type) modélise le bruit réel d’un capteur. Pour de la cryptographie, on utilise le module secrets, pas random.

02 / La bibliothèque standard

time et datetime : mesurer et dater

Trois horloges

perf_counter() : pour mesurer des durées (le plus précis). time.time() : secondes écoulées depuis le 1er janvier 1970, pour horodater. datetime.now() : date et heure lisibles. sleep() bloque le programme : sur un robot, on préfère souvent comparer l’heure à chaque tour de boucle plutôt que dormir (séance 20).

02 / La bibliothèque standard

statistics, collections, itertools : les outils malins

Médiane contre moyenne

Un capteur qui renvoie parfois une valeur aberrante fausse complètement la moyenne (90 tire tout vers le haut) mais pas la médiane. Les filtres médians sont partout en robotique. Counter remplace le comptage manuel de la séance 06 ; deque(maxlen=n) est une fenêtre glissante gratuite ; itertools.product énumère tous les cas d’un problème combinatoire — utile pour vérifier une table de vérité par force brute.

03 / Vos modules

Un fichier .py est un module

Le mystère de __name__

Quand on lance python outils.py, la variable __name__ vaut "__main__" et le bloc de tests s’exécute. Quand on fait import outils depuis un autre fichier, __name__ vaut "outils" et le bloc est ignoré : on récupère les fonctions sans lancer les tests. C’est la convention pour qu’un même fichier soit à la fois une bibliothèque et un programme. Vous verrez cette ligne dans presque tout code Python sérieux.

03 / Vos modules

Organiser un projet

mon_robot/
├── main.py          # point d'entrée : python main.py
├── capteurs.py      # lire_distance(), lire_batterie()
├── decision.py      # decider(etat)
├── moteurs.py       # avancer(), tourner()
├── outils.py        # maths, conversions
├── test_decision.py # tests (pytest)
└── donnees/
    └── journal.csv
# main.py
import capteurs
import decision
import moteurs

def cycle():
    etat = capteurs.lire_tout()
    action = decision.decider(etat)
    moteurs.executer(action)

if __name__ == "__main__":
    while True:
        cycle()
Principes

Un fichier par responsabilité, comme une fonction par tâche. main.py ne contient presque rien : il assemble. Les tests sont à côté, avec le préfixe test_. Les données ne sont jamais mélangées au code. Python trouve les modules dans le dossier du script lancé, puis dans la bibliothèque standard, puis dans les paquets installés : c’est sys.path. Un dossier contenant plusieurs modules s’appelle un paquet et s’importe avec des points : from mon_robot.capteurs import lire_distance.

04 / Bibliothèques externes

pip : installer ce que Python ne fournit pas

# Dans un terminal, sur votre PC
python -m pip install numpy
python -m pip install matplotlib pyserial

python -m pip list          # ce qui est installé
python -m pip show numpy    # détails d'un paquet
# Puis dans un programme
import numpy as np
import matplotlib.pyplot as plt
import serial   # pyserial : parler à l'Arduino
BibliothèqueUsage dans ce cours
numpyCalcul sur tableaux de mesures (séance 19)
matplotlibGraphiques (séance 19)
pyserialPort série ↔ Arduino / Pico (séance 20, 26)
pytestLancer les tests
pygameSimulateur de robot 2D
Environnements virtuels

Le dépôt PyPI contient plus de 500 000 paquets. Pour éviter que les projets se marchent dessus, on crée un environnement virtuel par projet : python -m venv .venv, puis l’activer (.venv\Scripts\activate sous Windows, source .venv/bin/activate ailleurs) avant de pip install. On liste les dépendances dans requirements.txt. Dans cette page, numpy est disponible sans installation ; la plupart des autres paquets ne le sont pas.

05 / Défis

Défi ★ — Le dé truqué et le chronomètre

Consigne

1. Simuler 10 000 lancers d’un dé à 6 faces et afficher la fréquence de chaque face avec Counter. Sont-elles proches de 1/6 ?
2. Mesurer avec perf_counter le temps pris par ces 10 000 lancers, puis par 1 000 000. Le rapport est-il de 100 ?

Correction
import random, time
from collections import Counter

for n in (10_000, 1_000_000):
    t0 = time.perf_counter()
    lancers = [random.randint(1, 6) for _ in range(n)]
    duree = time.perf_counter() - t0
    c = Counter(lancers)
    print(f"{n} lancers en {duree:.3f} s")
    for face in sorted(c):
        print(f"  face {face} : {c[face] / n:.4f}")

Le rapport des temps est proche de 100 : le temps est proportionnel au nombre de lancers. On dit que l’algorithme est linéaire. Séance 11.

05 / Défis

Défi ★★ — Estimer π par la méthode de Monte-Carlo

Consigne

On tire N points au hasard dans le carré [0,1]×[0,1]. La proportion de points à distance ≤ 1 de l’origine (dans le quart de disque) vaut π/4. Écrire estimer_pi(n) et comparer avec math.pi pour n = 1 000, 100 000, 1 000 000. Comment l’erreur évolue-t-elle ?

Correction
def estimer_pi(n):
    dedans = 0
    for _ in range(n):
        x, y = random.random(), random.random()
        if x * x + y * y <= 1:
            dedans += 1
    return 4 * dedans / n

for n in (1_000, 100_000, 1_000_000):
    p = estimer_pi(n)
    print(f"n={n:>9} π≈{p:.5f} erreur={abs(p - math.pi):.5f}")

L’erreur décroît environ comme 1/√n : pour gagner une décimale, il faut 100 fois plus de points. C’est lent mais ça marche pour des problèmes où aucune formule n’existe : les méthodes de Monte-Carlo ont été inventées pour le projet Manhattan et servent aujourd’hui en finance, en physique et en robotique (localisation d’un robot par « filtre à particules »).

05 / Défis

Défi ★★★ — Esprit prépa : un module de géométrie testé

Consigne

Écrire un fichier geometrie.py contenant : distance(p, q), angle(p, q) (angle en degrés du vecteur p→q, dans ]−180, 180]), rotation(p, theta_deg) (rotation du point p autour de l’origine), aire_polygone(points) (formule du lacet : ½ |Σ (xᵢ·yᵢ₊₁ − xᵢ₊₁·yᵢ)|). Avec un bloc __main__ de tests. L’importer et l’utiliser pour calculer l’aire d’un carré de côté 2 tourné de 45°.

Correction et ouverture
import math

def distance(p, q):
    return math.hypot(q[0] - p[0], q[1] - p[1])

def angle(p, q):
    return math.degrees(math.atan2(q[1] - p[1], q[0] - p[0]))

def rotation(p, theta_deg):
    t = math.radians(theta_deg)
    x, y = p
    return (x * math.cos(t) - y * math.sin(t), x * math.sin(t) + y * math.cos(t))

def aire_polygone(points):
    s = 0
    n = len(points)
    for i in range(n):
        x1, y1 = points[i]
        x2, y2 = points[(i + 1) % n]      # le dernier rejoint le premier
        s += x1 * y2 - x2 * y1
    return abs(s) / 2

if __name__ == "__main__":
    assert distance((0, 0), (3, 4)) == 5
    assert angle((0, 0), (0, 1)) == 90 and angle((0, 0), (-1, 0)) == 180
    r = rotation((1, 0), 90)
    assert math.isclose(r[0], 0, abs_tol=1e-12) and math.isclose(r[1], 1)
    assert aire_polygone([(0, 0), (2, 0), (2, 2), (0, 2)]) == 4
    print("geometrie : OK")

Le carré tourné : [geometrie.rotation(p, 45) for p in carre] puis aire_polygone → 4.0 (à 1e-15 près) : une rotation conserve l’aire, et votre code le vérifie numériquement. La formule du lacet est un cas particulier du théorème de Green, que vous verrez en prépa ; (i + 1) % n pour « boucler » sur le premier point est un idiome à retenir. Ces quatre fonctions sont exactement celles dont un robot a besoin pour naviguer (séance 14).

06 / Vérification

Après import math, que donne sqrt(4) ?

Deux questions supplémentaires

1. À quoi sert if __name__ == "__main__": ? À n’exécuter un bloc que lorsque le fichier est lancé directement, pas quand il est importé.

2. Pourquoi random.seed(0) ? Pour rendre une simulation reproductible : même graine, mêmes tirages.

Référence

Les mots à retenir

MotDéfinition
ModuleFichier .py importable.
PaquetDossier de modules.
Bibliothèque standardModules livrés avec Python (math, random…).
pipInstallateur de paquets externes (PyPI).
__name__Vaut "__main__" dans le fichier lancé.
Environnement virtuelInstallation Python isolée pour un projet.
Graine (seed)Valeur initiale du générateur pseudo-aléatoire.

Pour continuer

Vous avez toute la boîte à outils

Variables, conditions, boucles, fonctions, structures de données, fichiers, erreurs, modules. Il est temps de tout assembler : séance suivante, un projet complet.

À faire chez soi

Documentation

Tutoriel officiel : modules · Index de la bibliothèque standard · PyPI

← Séance 08SommaireSéance 10 : Projet →