Créer une liste de pique-nique réutilisable

PythonBeginner
Pratiquer maintenant

Introduction

Des amis préparent un pique-nique. Deux recettes partagent des ingrédients, certaines provisions sont déjà disponibles et le magasin vend des paquets entiers. Vous devez terminer un programme Python réutilisable qui transforme ces informations en une véritable liste de courses.

Vous exécuterez d'abord un petit cas fonctionnel, puis améliorerez le module de calcul pour créer un CSV complet. Le code de picnic.py pour les options et l'écriture CSV ainsi que les recettes synthétiques sont fournis. Ce laboratoire suppose les listes, dictionnaires, boucles, fonctions, exceptions élémentaires de Python et la saisie de base au terminal. Les nouveaux formats et options sont expliqués ici. La durée inclut lecture et erreurs courantes ; c'est une estimation de l'auteur, non une mesure auprès de débutants.

Lire une petite liste de pique-nique

Dans cette étape, vous générerez une liste pour trois personnes et une salade, puis relierez chaque ligne CSV à son entrée JSON. Commencez dans WebIDE, ouvrez le menu Terminal et choisissez New Terminal. Toutes les commandes du laboratoire se saisissent dans ce terminal. Les fichiers préparés ne dépendent d'aucun laboratoire précédent.

Vous devez connaître listes, dictionnaires, boucles, fonctions et exceptions élémentaires de Python. JSON, l'écriture CSV et les options utilisées sont expliqués ci-dessous. Les quantités sont entières : grammes (g), millilitres (ml) ou unités (each), avec une unité par ID d'ingrédient. Il ne s'agit ni de conversion d'unités ni de nutrition.

Partir d'un petit cas fonctionnel

cd change le répertoire courant. Un chemin absolu commence par / ; un chemin relatif ultérieur, comme data/menu.json, est résolu depuis le répertoire courant. Si un chemin échoue après un déplacement, revenez ici avec le même cd.

cd /home/labex/project/picnic

cat affiche un fichier texte. Lisez le petit menu :

cat data/sample-menu.json

JSON utilise {} pour les objets (chargés en dictionnaires Python), [] pour les listes et des guillemets doubles pour les clés textuelles. Chaque recette possède un objet ingredients : "chickpea": 70 signifie 70 g par personne, pas 70 paquets. chickpea est un ID stable, tandis que name est un libellé affiché. Deux produits peuvent porter le même nom sans être interchangeables.

cat data/products.json

La table des produits fournit name, unit et un entier positif pack_size. Les pois chiches se vendent par 200 g. Le petit cas utilise data/empty-pantry.json, dont l'objet vide {} signifie qu'il n'y a aucun stock. Les autres quantités fournies sont des entiers non négatifs. Un ID absent du garde-manger signifie aussi zéro stock ; un produit absent est une erreur, car sa taille de paquet est inconnue.

Le code fourni dans picnic.py traite les options et l'écriture CSV. python3 l'exécute. Une option commençant par -- nomme la valeur suivante : menu, provisions, produits, nombre positif de personnes ou chemin CSV de sortie. --output désigne un fichier à créer ou remplacer : utilisez les nouveaux noms indiqués. Les JSON sources restent inchangés.

python3 picnic.py --menu data/sample-menu.json --pantry data/empty-pantry.json --products data/products.json --people 3 --output sample.csv

Message attendu :

Created 2 ingredient rows in sample.csv
cat sample.csv

L'en-tête nomme les colonnes. Les pois chiches nécessitent 70 * 3 = 210 g. Deux paquets de 200 g apportent 400 g, avec un reste de 190 g. Les tomates nécessitent 120 g : un paquet de 250 g laisse 130 g. Ce petit cas fonctionne, mais un menu partagé et du stock révéleront deux défauts du programme initial.

Le CSV final contient une ligne par ID d'ingrédient nécessaire et les colonnes ingredient_id,name,unit,required,stock,shortfall,packs,leftover. stock représente le stock initial, pas seulement la quantité consommée. leftover comprend le stock initial inutilisé. L'ordre des lignes et colonnes est libre, mais tous les champs et relations doivent être exacts. Conservez picnic.py, un programme de calcul réutilisable et tous les originaux de data/. Les calculs Python équivalents sont acceptés ; l'interface et les formats restent identiques.

Regrouper les ingrédients communs par ID

Dans cette étape, vous corrigerez le calcul des besoins communs avant de décider des achats. Continuez dans le même terminal de WebIDE.

cat data/menu.json

La salade utilise 70 g de pois chiches par personne et les wraps 40 g. Pour cinq personnes, le total doit être (70 + 40) * 5 = 550 g. Acheter séparément pour chaque recette peut entraîner des paquets superflus. Regroupez d'abord tous les besoins, puis déduisez le stock une fois et arrondissez le manque une fois.

cat calculations.py

Dans total_requirements, le programme initial affecte chaque fois amount * people à l'ID. La seconde recette remplace donc le besoin de la première pour le même ID. totals.get(ingredient_id, 0) lit au contraire le total déjà accumulé, ou zéro pour un nouvel ID. Ajoutez-y la quantité de la recette suivante multipliée par le nombre de personnes.

Remplacer le petit module de calcul

La commande suivante écrit tout le module pour conserver une indentation et des limites de fonctions claires. cat > calculations.py redirige le texte vers ce fichier et remplace son contenu. <<'PYCODE' démarre un here-document : collez tout jusqu'à la dernière ligne PYCODE. Les guillemets du délimiteur empêchent le shell d'interpréter le texte Python. Seul calculations.py est remplacé ; les JSON et CSV restent intacts. En cas de faute de frappe, réexécutez le bloc entier. L'indentation Python compte : conservez les blocs de quatre espaces.

cat > calculations.py <<'PYCODE'
"""The two calculation functions you will improve."""


def total_requirements(menu, people):
    totals = {}
    for recipe in menu["recipes"]:
        for ingredient_id, amount in recipe["ingredients"].items():
            totals[ingredient_id] = totals.get(ingredient_id, 0) + amount * people
    return totals


def shopping_rows(totals, pantry, products):
    rows = []
    for ingredient_id, required in totals.items():
        product = products[ingredient_id]
        size = product["pack_size"]
        packs = (required + size - 1) // size
        rows.append({"ingredient_id": ingredient_id,
                     "name": product["name"], "unit": product["unit"],
                     "required": required, "stock": 0,
                     "shortfall": required, "packs": packs,
                     "leftover": packs * size - required})
    return rows
PYCODE

Seul le regroupement est corrigé pour l'instant. Générez la liste principale pour cinq personnes :

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv

Le champ required des pois chiches doit désormais valoir 550 ; les tomates demandent 200 et les wraps 5. stock reste nul car la seconde fonction ignore encore les provisions. Cette liste intermédiaire n'est pas prête pour les courses.

Acheter des paquets entiers après utilisation du stock

Dans cette étape, vous terminerez le calcul des achats et lirez une liste qui tient compte des provisions disponibles. Continuez dans le terminal de WebIDE.

cat data/pantry.json

Pour cinq personnes, les pois chiches nécessitent 550 g avec 150 g en stock : il manque 400 g, exactement deux paquets de 200 g. Les tomates nécessitent 200 g mais 250 g sont disponibles : manque et achat sont nuls, et il reste 50 g. Il faut 5 wraps, dont 2 sont disponibles ; les 3 manquants nécessitent un paquet de 6 et il en reste 3 au final.

Utilisez max(0, required - stock) pour qu'un surplus ne produise pas d'achat négatif. Pour un manque entier non négatif s et une taille positive p, (s + p - 1) // p arrondit au nombre supérieur de paquets ; // est la division entière avec arrondi inférieur. Avec des paquets de 200 g, un manque de 0 donne 0 paquet, 200 donne 1 et 201 donne 2. Les limites exactes sont ainsi correctes sans calcul flottant.

Le reste final est stock + packs * pack_size - required. Calculer seulement packs * pack_size - shortfall oublierait les provisions inutilisées lorsque le stock dépasse les besoins.

Remplacez le module par les deux fonctions terminées. Le here-document fonctionne comme à l'étape précédente.

cat > calculations.py <<'PYCODE'
"""Calculate requirements before deciding what to buy."""


def total_requirements(menu, people):
    totals = {}
    for recipe in menu["recipes"]:
        for ingredient_id, amount in recipe["ingredients"].items():
            totals[ingredient_id] = totals.get(ingredient_id, 0) + amount * people
    return totals


def shopping_rows(totals, pantry, products):
    rows = []
    for ingredient_id, required in totals.items():
        product = products[ingredient_id]
        stock = pantry.get(ingredient_id, 0)
        shortfall = max(0, required - stock)
        size = product["pack_size"]
        packs = (shortfall + size - 1) // size
        rows.append({"ingredient_id": ingredient_id,
                     "name": product["name"], "unit": product["unit"],
                     "required": required, "stock": stock,
                     "shortfall": shortfall, "packs": packs,
                     "leftover": stock + packs * size - required})
    return rows
PYCODE
python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
cat five.csv

Contenu attendu, avec un ordre éventuellement différent pour une solution équivalente :

ingredient_id,name,unit,required,stock,shortfall,packs,leftover
chickpea,Chickpeas,g,550,150,400,2,0
tomato,Tomatoes,g,200,250,0,0,50
wrap,Wraps,each,5,2,3,1,3

La liste est exploitable : achetez deux paquets de pois chiches, aucune tomate et un paquet de wraps. Conservez les lignes sans achat afin de distinguer « déjà couvert » d'un ingrédient oublié. Seuls les ID nécessaires à ce menu demandent une ligne ; les autres provisions n'en demandent pas.

Réutiliser le programme et corriger les erreurs de saisie

Dans cette étape, vous comprendrez le code fourni dans picnic.py pour écrire les fichiers, réutiliserez les calculs pour un autre menu et corrigerez des erreurs courantes. Continuez dans le terminal de WebIDE. Aucune variable ni session antérieure n'est nécessaire : chaque commande fournit explicitement ses chemins d'entrée.

cat picnic.py

argparse lit les options nommées et fournit --help ; type=int rejette les nombres de personnes non entiers et la vérification suivante rejette zéro et les valeurs négatives. load_json utilise open(..., encoding="utf-8") et json.load pour lire des objets structurés complets. Un bloc with ferme le fichier à sa fin. Le module de calcul renvoie une liste de dictionnaires de lignes.

Toutes les entrées et tous les calculs sont traités avant d'ouvrir la sortie. La vérification des produits compare les ID nécessaires aux ID disponibles et lève ValueError si une information d'emballage manque. csv.DictWriter écrit les colonnes nommées, l'en-tête et les lignes. newline="" laisse le module CSV gérer les fins de ligne ; UTF-8 conserve les noms. Ne remplacez pas l'écriture CSV par une concaténation avec des virgules : un futur nom contenant une virgule doit rester un seul champ.

Le code fourni dans picnic.py intercepte les erreurs de fichier, de JSON/valeur et de clé absente, affiche une explication sur la sortie d'erreur standard et termine avec un code non nul. Il ne garantit pas la protection d'une écriture disque interrompue ; un nombre de personnes invalide ou un produit absent doit toutefois laisser une sortie existante intacte.

Lire un échec attendu et reprendre

Les deux commandes suivantes échouent volontairement. L'erreur est attendue et le terminal reste utilisable. La première demande zéro personne et doit afficher people must be a positive integer avant de remplacer five.csv.

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/products.json --people 0 --output five.csv

La seconde contient une faute dans le nom du menu. Attendez-vous à Cannot make list: suivi d'un fichier introuvable, pas à une liste vide déclarée réussie. Vérifiez les noms dans data/ et recommencez avec le bon chemin.

python3 picnic.py --menu data/menus.json --pantry data/pantry.json --products data/products.json --people 5 --output five.csv
ls data

missing-products.json est volontairement incomplet : des produits du menu y manquent. La commande suivante doit afficher missing product: et préserver five.csv. Une taille de paquet inconnue n'équivaut ni à zéro stock ni à zéro achat.

python3 picnic.py --menu data/menu.json --pantry data/pantry.json --products data/missing-products.json --people 5 --output five.csv
cat five.csv

La liste valide pour cinq personnes doit toujours correspondre à l'étape précédente. Reprenez avec le fichier complet des produits, sans changer les données originales.

Préparer une autre liste

cat data/other-menu.json

Ce menu ne contient que des tomates et des wraps. Pour quatre personnes, il faut 200 g de tomates et 8 wraps. Le même programme doit utiliser le stock original sans conserver les ingrédients de l'exécution précédente.

python3 picnic.py --menu data/other-menu.json --pantry data/pantry.json --products data/products.json --people 4 --output other.csv
cat other.csv

Les tomates ne nécessitent aucun achat et laissent 50 g. Il manque 6 wraps, soit exactement un paquet, sans reste. Aucune ligne de pois chiches ne doit apparaître. Vos fichiers five.csv, sample.csv, le programme et les JSON originaux restent disponibles. Un autre menu et un nouveau nom de sortie permettent de conserver les deux plans. Les contrôles lisent vos fichiers réels et exécutent le programme inchangé sur des cas temporaires ; ils ne créent ni ne réparent vos listes sur place.

Résumé

Vous avez regroupé les besoins par personne selon l'ID d'ingrédient, déduit le stock une seule fois et arrondi uniquement le manque à des paquets entiers. Vous avez produit de vrais CSV, conservé les lignes pertinentes sans achat, réutilisé le programme pour un autre menu et corrigé des entrées invalides sans remplacer un résultat valide.