Session pratique
Durée2hConsignes globales
Dans cette session, vous allez concevoir, un petit système d’analyse de données issues d’un capteur de température. Votre application ne doit jamais s’arrêter sur une erreur, même en cas :
- de données corrompues,
- de valeurs aberrantes,
- de panne du capteur.
Pour cela, vous allez appliquer trois principes tout au long de la session :
- la programmation défensive : chaque méthode gère explicitement les exceptions qui peuvent survenir,
- le développement guidé par les tests : les tests fournis décrivent le comportement attendu avant que vous n’écriviez le code,
- la documentation : chaque méthode publique est documentée (paramètres, valeur de retour, exceptions levées) avec des docstrings.
Important
Le but de cette session est de vous aider à maîtriser des notions importantes en informatique. Un assistant de programmation intelligent tel que GitHub Copilot, que vous avez peut-être déjà installé, sera capable de vous fournir une solution à ces exercices basée uniquement sur un nom de fichier judicieusement choisi.
Dans un but d’entraînement, nous vous conseillons de désactiver ces outils en premier.
À la fin de l’activité pratique, nous suggérons que vous travailliez sur l’exercice à nouveau avec ces outils activés. Suivre ces deux étapes améliorera vos compétences à la fois fondamentalement et pratiquement.
Contenu de l’activité
Préparation de l’environnement
Avant de commencer, créez un projet uv dédié à cette séance. Dans un terminal, placez-vous dans le dossier de la séance (par exemple imt/s5/info/prog/session2), puis lancez la commande suivante :
Vous écrirez tous les fichiers de cette séance dans ce projet, en ajoutant les bibliothèques dont vous avez besoin avec uv add et en exécutant vos programmes dans l’environnement virtuel associé. Au besoin, revoyez la fiche Gestion moderne de projet Python avec uv.
Organisation
Vous allez écrire trois fichiers Python, tous placés dans le même répertoire de travail :
sensor.py: une classeSensorqui génère des mesures de température et les écrit dans un fichier,analyzer.py: une classeAnalyzerqui calcule des statistiques sur les mesures reçues et les trace dans un fichier de log,reader.py: une classeReaderqui lit le fichier de mesures et transmet chaque ligne à unAnalyzer.

Pour chaque classe, un fichier de tests unitaires est fourni ci-dessous : copiez-collez-le tel quel dans un fichier tests_<nom>.py du même répertoire, puis complétez la classe correspondante jusqu’à ce que tous ces tests passent.
Exercice 1 : la classe Sensor
Implémentez une classe Sensor qui simule un capteur de données suivant une loi gaussienne (moyenne, écart-type) et qui tombe en panne une fois sur dix en moyenne.
Attributs (avec leurs valeurs par défaut) :
Méthodes à implémenter :
| Méthode | Entrée | Sortie | Exceptions à gérer |
|---|---|---|---|
__init__ |
les 5 attributs ci-dessus, tous optionnels | — | — |
get_id, get_mean, get_std, get_sleep_time, get_storage_file_name |
— | valeur de l’attribut correspondant | — |
get_value |
— | float compris entre 0 et 150 |
lève OutofBoundsException si la valeur générée est hors de [0, 150] ; lève NotRespondingException une fois sur dix en moyenne (avant même de générer une valeur) |
run |
— | None (boucle infinie ; écrit dans __storage_file_name chaque valeur produite par get_value, ou OoB/NR si une exception est levée) |
doit intercepter KeyboardInterrupt (arrêt propre du programme), IOError (fichier illisible/inaccessible, message d’erreur puis arrêt) et toute autre Exception (message d’erreur puis arrêt) |
OutofBoundsException et NotRespondingException sont deux classes d’exception à définir vous-même dans sensor.py (héritant de Exception).
Copiez-collez le fichier de tests suivant dans tests_sensor.py :
Exécutez uv run python tests_sensor.py jusqu’à ce que tous les tests passent.
Avant de continuer, vérifiez :
-
OutofBoundsExceptionetNotRespondingExceptionsont bien levées et interceptées aux bons endroits -
uv run python tests_sensor.pypasse sans échec - chaque méthode publique de
Sensora une docstring précisant paramètres, valeur de retour et exceptions levées
Exercice 2 : la classe Analyzer
Implémentez une classe Analyzer qui reçoit des valeurs (sous forme de texte) une par une, les convertit en float, et calcule des statistiques toutes les __buffer_size valeurs reçues.
Attributs :
Méthodes à implémenter :
| Méthode | Entrée | Sortie | Exceptions à gérer |
|---|---|---|---|
__init__ |
buffer_size: int = 10, log_file: str = "analyzer.log" |
— | — |
get_values, get_errors, get_buffer_size, get_logger |
— | valeur de l’attribut correspondant | — |
add_data |
value: str |
None (ajoute value convertie à __values, journalise Added value:<value> en debug ; déclenche log_analysis toutes les __buffer_size valeurs ajoutées) |
intercepte ValueError : si value n’est pas convertible en float, elle est ajoutée à __errors et journalisée en error (Error adding value:<value>) au lieu d’être ajoutée à __values |
log_analysis |
— | dict avec les clés average, min, max calculées sur les __buffer_size dernières valeurs (None si __values est vide) ; journalise aussi le résultat en info |
— |
Copiez-collez le fichier de tests suivant dans tests_analyzer.py :
Exécutez uv run python tests_analyzer.py jusqu’à ce que tous les tests passent.
Avant de continuer, vérifiez :
- la conversion invalide (
ValueError) est interceptée et n’interrompt jamaisadd_data -
uv run python tests_analyzer.pypasse sans échec - chaque méthode publique de
Analyzera une docstring précisant paramètres, valeur de retour et cas particuliers gérés
Exercice 3 : la classe Reader
Implémentez une classe Reader qui lit en continu le fichier écrit par le capteur et transmet chaque ligne à un Analyzer.
Attributs :
Méthodes à implémenter :
| Méthode | Entrée | Sortie | Exceptions à gérer |
|---|---|---|---|
__init__ |
analyzer: Analyzer, sleep_time: float = 1.0, file_name: str = "data.txt" |
— | — |
get_analyzer, get_sleep_time, get_file_name |
— | valeur de l’attribut correspondant | — |
process_line |
line: str |
None (les lignes commençant par # sont ignorées, les autres sont transmises à self.__analyzer.add_data) |
— |
run |
— | None (boucle infinie : ouvre __file_name en lecture, lit une ligne, la traite avec process_line si elle existe, sinon attend __sleep_time secondes) |
KeyboardInterrupt → arrêt propre ; FileNotFoundError → message d’erreur puis nouvelle tentative après 2 * __sleep_time secondes ; IOError → idem ; Exception → idem |
Copiez-collez le fichier de tests suivant dans tests_reader.py :
Exécutez uv run python tests_reader.py jusqu’à ce que tous les tests passent.
Avant de continuer, vérifiez :
- les quatre exceptions de
runsont interceptées séparément, avec le bon comportement pour chacune -
uv run python tests_reader.pypasse sans échec - chaque méthode publique de
Readera une docstring précisant paramètres, valeur de retour et exceptions gérées
Test de bout en bout
Ajoutez à la fin de sensor.py :
et à la fin de reader.py :
Ouvrez deux terminaux dans votre répertoire de travail et lancez-y respectivement python sensor.py et python reader.py, dans n’importe quel ordre. Le lecteur doit patienter tant que le fichier n’existe pas ou est vide, puis afficher/journaliser les analyses au fur et à mesure. Arrêtez et relancez le capteur (Ctrl-C puis nouvelle exécution) : le lecteur ne doit pas s’arrêter pour autant.
Avant de conclure, vérifiez :
- le lecteur survit à l’absence, puis à l’arrêt, du capteur sans planter
-
pylint sensor.py analyzer.py reader.pyne signale plus de violation majeure de la PEP8 - la documentation générée par
pydoc -w sensor analyzer readerest complète et compréhensible sans lire le code
Pour aller plus loin
Si le temps le permet :
- Plusieurs types de capteurs. Ajoutez un second type de capteur qui retourne un booléen (test de bon fonctionnement) au lieu d’un réel, en préfixant chaque écriture par le nom et le type du capteur (ex.
sensor1:BinarySensor:1). Documentez et vérifiez la PEP8 ; les tests fournis ne couvrent pas cette extension, à vous d’en écrire de nouveaux si vous le souhaitez. - Plusieurs analyseurs. Faites en sorte qu’un
Readerpuisse envoyer les données de chaque capteur à un analyseur dédié (statistiques par capteur, et un analyseur d’erreurs séparé déclenché à l’arrêt du programme plutôt qu’à intervalle régulier). - Parallélisme. Plutôt que deux terminaux séparés, utilisez le module
threadingpour exécuter le capteur et le lecteur en parallèle dans un même programme. Vous découvrirez en S6 des mécanismes de communication inter-processus (IPC) plus adaptés qu’un fichier partagé pour ce genre d’échange.