Eine wiederverwendbare Picknickliste erstellen

PythonBeginner
Jetzt üben

Einführung

Freunde planen ein Picknick. Zwei Rezepte benötigen teilweise dieselben Zutaten, einige Vorräte sind schon vorhanden und im Laden gibt es nur ganze Packungen. Du vervollständigst ein wiederverwendbares Python-Programm, das daraus eine brauchbare Einkaufsliste erstellt.

Zuerst führst du ein kleines funktionierendes Beispiel aus, dann verbesserst du das Berechnungsmodul und erstellst eine vollständige CSV-Datei. Der Code in picnic.py für Optionen und CSV-Ausgabe sowie synthetische Rezepte sind vorbereitet. Vorausgesetzt werden Python-Listen, Dictionaries, Schleifen, Funktionen, grundlegende Exceptions und einfache Terminaleingaben. Neue Dateiformate und Optionen werden erklärt. Die Zeitangabe umfasst Lesen und gewöhnliche Fehler; sie ist eine Autorenschätzung, keine gemessene Bearbeitungszeit für Anfänger.

Eine kleine Picknickliste lesen

In diesem Schritt erstellst du eine Einkaufsliste für einen Salat für drei Personen und ordnest jede CSV-Zeile der JSON-Eingabe zu. Beginne in WebIDE. Öffne dort Terminal und wähle New Terminal. Alle Befehle dieses Labs gehören in dieses Terminal. Die vorbereiteten Dateien sind von früheren Labs unabhängig.

Du benötigst Python-Listen, Dictionaries, Schleifen, Funktionen und grundlegende Exceptions. JSON-Dateien, CSV-Ausgabe und die verwendeten Optionen werden unten erklärt. Mengen sind ganzzahlig: Gramm (g), Milliliter (ml) oder Stück (each), mit einer Einheit je Zutaten-ID. Einheitenumrechnung und Ernährungsplanung gehören nicht dazu.

Mit einem funktionierenden kleinen Fall beginnen

cd wechselt das aktuelle Verzeichnis. Absolute Pfade beginnen mit /; spätere relative Pfade wie data/menu.json werden vom aktuellen Verzeichnis aus aufgelöst. Falls ein Pfad nach einem Verzeichniswechsel fehlschlägt, kehre mit demselben cd zurück.

cd /home/labex/project/picnic

cat gibt eine Textdatei aus. Lies das kleine Menü:

cat data/sample-menu.json

JSON verwendet {} für Objekte, die als Python-Dictionaries geladen werden, [] für Listen und doppelte Anführungszeichen für Zeichenkettenschlüssel. Jedes Rezept enthält ein Objekt ingredients: "chickpea": 70 bedeutet 70 g pro Person, nicht 70 Packungen. chickpea ist eine stabile ID; name ist eine Anzeige. Zwei Produkte können denselben Namen haben, ohne austauschbar zu sein.

cat data/products.json

Die Produkttabelle enthält name, unit und eine positive ganze Zahl pack_size. Kichererbsen gibt es in 200-g-Packungen. Das kleine Beispiel verwendet data/empty-pantry.json: das leere Objekt {} bedeutet keinen Vorrat. Die anderen vorbereiteten Mengen sind nichtnegative ganze Zahlen. Eine fehlende Vorrats-ID bedeutet ebenfalls null Vorrat; ein fehlendes Produkt ist ein Fehler, weil seine Packungsgröße unbekannt ist.

Der bereitgestellte Code in picnic.py verarbeitet Optionen und CSV-Ausgabe. python3 führt ihn aus. Optionen mit -- benennen den folgenden Wert: Menü, Vorräte, Produkttabelle, positive Personenzahl und CSV-Ausgabepfad. --output benennt eine Datei zum Erstellen oder Ersetzen; verwende deshalb die gezeigten neuen Namen. Die ursprünglichen JSON-Dateien bleiben erhalten.

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

Erwartete Meldung:

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

Die Kopfzeile benennt die Spalten. Kichererbsen benötigen 70 * 3 = 210 g. Zwei Packungen zu 200 g liefern 400 g, also bleiben 190 g. Tomaten benötigen 120 g; eine 250-g-Packung lässt 130 g übrig. Dieser kleine Fall funktioniert, aber ein gemeinsames Menü und vorhandene Vorräte werden zwei Fehler im Startprogramm sichtbar machen.

Die endgültige CSV enthält eine Zeile je benötigter Zutaten-ID und die Spalten ingredient_id,name,unit,required,stock,shortfall,packs,leftover. stock ist der ursprüngliche Vorrat, nicht nur die verbrauchte Menge. leftover schließt ungenutzten ursprünglichen Vorrat ein. Zeilen- und Spaltenreihenfolge sind frei; alle Felder und Beziehungen müssen stimmen. Bewahre picnic.py, ein wiederverwendbares Berechnungsprogramm und alle Originale in data/ auf. Gleichwertige Python-Berechnungen sind erlaubt; Schnittstelle und Formate bleiben gleich.

Gemeinsame Zutaten nach ID zusammenfassen

In diesem Schritt korrigierst du den gemeinsamen Bedarf, bevor du Einkaufsentscheidungen triffst. Bleibe im selben WebIDE-Terminal.

cat data/menu.json

Der Salat benötigt pro Person 70 g Kichererbsen, die Wraps 40 g. Für fünf Personen muss die Summe (70 + 40) * 5 = 550 g betragen. Einzelne Einkäufe je Rezept können zu überzähligen Packungen führen. Fasse zuerst den ganzen Bedarf zusammen, ziehe dann den Vorrat einmal ab und runde nur die verbleibende Fehlmenge einmal auf.

cat calculations.py

In total_requirements weist das Startprogramm der ID jedes Mal amount * people zu. Das zweite Rezept mit derselben ID ersetzt den ersten Bedarf. totals.get(ingredient_id, 0) liest hingegen die bisherige Summe oder null bei einer neuen ID. Addiere die mit der Personenzahl skalierte Menge des nächsten Rezepts.

Das kleine Berechnungsmodul ersetzen

Der folgende Befehl schreibt das vollständige Modul, damit Einrückung und Funktionsgrenzen klar bleiben. cat > calculations.py leitet Text in diese Datei um und ersetzt deren Inhalt. <<'PYCODE' beginnt ein Here-Dokument: Füge alles bis zur letzten Zeile PYCODE ein. Anführungszeichen um den Begrenzer verhindern, dass die Shell den Python-Text interpretiert. Nur calculations.py wird ersetzt; JSON und CSV bleiben unangetastet. Bei einem Tippfehler führe den gesamten Block erneut aus. Python-Einrückung ist bedeutungsvoll: Bewahre Blöcke mit vier Leerzeichen.

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

Bisher ist nur das Zusammenfassen korrigiert. Erstelle die Hauptliste für fünf Personen:

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

Das Feld required der Kichererbsen muss jetzt 550 sein; Tomaten benötigen 200, Wraps 5. stock ist noch null, weil die zweite Funktion die Vorräte ignoriert. Diese Zwischenliste ist noch nicht einkaufsfertig.

Nach dem Vorratsabzug ganze Packungen kaufen

In diesem Schritt vervollständigst du die Einkaufsberechnung und liest eine Liste, die vorhandene Vorräte berücksichtigt. Arbeite weiter im WebIDE-Terminal.

cat data/pantry.json

Für fünf Personen werden 550 g Kichererbsen benötigt, 150 g sind vorhanden: Es fehlen 400 g. Zwei Packungen zu 200 g decken das genau. Tomaten benötigen 200 g, vorhanden sind 250 g: Fehlmenge und Einkauf sind null, und 50 g bleiben übrig. Für 5 Wraps sind 2 vorhanden; die Fehlmenge 3 verlangt eine Packung mit 6 Stück, danach bleiben 3 übrig.

Nutze max(0, required - stock), damit Überschuss keinen negativen Einkauf erzeugt. Für die nichtnegative ganzzahlige Fehlmenge s und positive Packungsgröße p rundet (s + p - 1) // p auf ganze Packungen auf; // ist die ganzzahlige Division mit Abrundung. Bei 200-g-Packungen verlangt die Fehlmenge 0 genau 0 Packungen, 200 verlangt 1 und 201 verlangt 2. Das behandelt exakte Grenzen ohne Gleitkommaarithmetik.

Der endgültige Rest ist stock + packs * pack_size - required. Nur packs * pack_size - shortfall zu berechnen würde ungenutzte Vorräte vergessen, sobald der Vorrat den Bedarf übersteigt.

Ersetze das Modul durch beide vollständigen Funktionen. Das Here-Dokument funktioniert wie zuvor.

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

Erwarteter Inhalt; eine gleichwertige Implementierung darf die Reihenfolge ändern:

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

Die Liste ist jetzt nutzbar: Kaufe zwei Packungen Kichererbsen, keine Tomaten und eine Packung Wraps. Behalte Zeilen ohne Einkauf, damit „bereits gedeckt“ von versehentlich ausgelassenen Zutaten unterscheidbar ist. Nur die für dieses Menü benötigten IDs brauchen Zeilen; andere Vorräte nicht.

Das Programm wiederverwenden und Eingabefehler beheben

In diesem Schritt verstehst du den bereitgestellten Schreibcode in picnic.py, nutzt deine Berechnungen für ein anderes Menü und behebst gewöhnliche Fehler. Bleibe im WebIDE-Terminal. Variablen oder frühere Shell-Sitzungen sind nicht erforderlich: Jeder Befehl übergibt seine Eingabepfade ausdrücklich.

cat picnic.py

argparse liest benannte Optionen und stellt --help bereit. type=int weist nichtganzzahlige Personenzahlen zurück, die nächste Prüfung null und negative Zahlen. load_json liest mit open(..., encoding="utf-8") und json.load vollständige strukturierte Objekte. Ein with-Block schließt die Datei an seinem Ende. Das Berechnungsmodul gibt eine Liste von Zeilen-Dictionaries zurück.

Alle Eingaben und Berechnungen werden vor dem Öffnen der Ausgabe verarbeitet. Die Produktprüfung vergleicht benötigte mit vorhandenen Produkt-IDs und löst bei fehlenden Packungsdaten ValueError aus. csv.DictWriter schreibt benannte Spalten, Kopfzeile und Zeilen. newline="" überlässt dem CSV-Modul die Zeilenenden; UTF-8 erhält Namen. Ersetze CSV-Schreiben nicht durch Zusammenfügen mit Kommas: Ein künftiger Produktname mit Komma muss ein einzelnes Feld bleiben.

Der bereitgestellte Code in picnic.py fängt Datei-, JSON-/Wert- und fehlende Schlüsselfehler ab, erklärt sie auf der Standardfehlerausgabe und endet mit einem Status ungleich null. Er garantiert keinen Schutz bei unterbrochenem Schreiben auf den Datenträger; ungültige Personenzahlen und fehlende Produkte müssen eine vorhandene Ausgabe jedoch unverändert lassen.

Einen erwarteten Fehler lesen und fortfahren

Die nächsten zwei Befehle scheitern absichtlich. Die Fehlermeldung wird erwartet; das Terminal bleibt bedienbar. Der erste verlangt null Personen und muss vor dem Ersetzen von five.csv die Meldung people must be a positive integer ausgeben.

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

Der zweite schreibt den Menünamen falsch. Erwarte Cannot make list: mit einer Meldung über die fehlende Datei, keine scheinbar erfolgreiche leere Liste. Prüfe die Namen in data/ und wiederhole den Befehl mit dem richtigen Pfad.

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 ist absichtlich unvollständig und enthält nicht alle benötigten Produkte. Der nächste Befehl muss missing product: ausgeben und five.csv bewahren. Unbekannte Packungsdaten bedeuten weder null Vorrat noch null Einkauf.

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

Die gültige Fünf-Personen-Liste muss weiterhin dem vorigen Schritt entsprechen. Fahre mit der vollständigen Produktdatei fort, statt Originaldaten zu ändern.

Eine andere Einkaufsliste erstellen

cat data/other-menu.json

Dieses Menü enthält nur Tomaten und Wraps. Für vier Personen benötigt es 200 g Tomaten und 8 Wraps. Dasselbe Programm soll den ursprünglichen Vorrat verwenden und keine Zutaten aus dem letzten Lauf übernehmen.

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

Tomaten brauchen keinen Einkauf; 50 g bleiben. Bei Wraps fehlen 6, genau eine Packung ohne Rest. Eine Kichererbsenzeile darf nicht vorkommen. five.csv, sample.csv, Programm und ursprüngliche JSON-Dateien bleiben verfügbar. Ein anderes Menü mit neuem Ausgabenamen erhält beide Pläne. Die Prüfungen lesen deine echten Ausgaben und führen das unveränderte Programm mit temporären Eingaben aus; sie erzeugen oder reparieren keine Listen in deinem Arbeitsbereich.

Zusammenfassung

Du hast Bedarf pro Person nach Zutaten-ID zusammengefasst, Vorräte einmal abgezogen und nur die Fehlmenge auf ganze Packungen aufgerundet. Du hast echte CSV-Listen erstellt, sinnvolle Zeilen ohne Einkauf bewahrt, das Programm für ein anderes Menü wiederverwendet und ungültige Eingaben behoben, ohne ein korrektes Ergebnis zu ersetzen.