Seite 1 von 1

Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Freitag 25. November 2022, 19:35
von voldemort_73
Guten Abend, könnte mir bitte jemand bei dem Vergleich des Datums via Pettern Matching helfen, Aufgabe b). Stehe gerade leider auf dem Schlauch.

In dieser Aufgabe sollen Sie die Lagerbestände eines Supermarkts modellieren. Der
Lagerbestand einer Ware ist entweder verzehrbar oder nicht-verzehrbar.
Sie können die gesamte Funktionalität mit dem beigefügten Skript test_supermarket.py
testen. Das Testskript soll nicht verändert werden.

(a) Erstellen Sie zunächst zwei Datenklassen Food und NonFood, wobei NonFood ei-
ne leere Klasse ist und Food ein Mindesthaltbarkeitsdatum enthält. Modellieren
Sie das Datum dabei als einen String, der für den 14. Februar 2021 das Format
"2021-02-14" hat1 . Dieses Format erlaubt es, die lexikographische Ordnung zu
verwenden, um herauszufinden ob eines von zwei Daten weiter in der Zukunft
liegt als das andere.
Erstellen Sie die Datenklasse Stock mit folgenden Feldern: Namen der Ware
name, Anzahl der gelagerten Waren units, Stückpreis in Cents price_per_unit
sowie kind. Das Feld kind soll eine Instanz der Datenklassen Food oder NonFood
enthalten, definieren Sie es also als Union-Typ über die beiden Datenklassen.

(b) Schreiben Sie eine Funktion is_expired, die einen Lagerbestand und ein Da-
tum als Argumente nimmt und zurückgibt, ob der Lagerbestand zu diesem
Datum abgelaufen ist. Verzehrbare Waren gelten dabei ab einem Tag nach ih-
rem Mindesthaltbarkeitsdatum als abgelaufen. Nicht-verzehrbare Waren gelten
nie als abgelaufen. Verwenden Sie Pattern Matching, um den Inhalt des Feldes
kind zu prüfen.


Hier mein Ansatz:

Code: Alles auswählen

from dataclasses import dataclass


@dataclass
class Food:
    mhd: str


@dataclass
class NonFood:
    pass


@dataclass
class Stock:
    name: str # name of the goods
    units: int # amount of the goods
    price_per_unit: int # in cents
    kind: Food | NonFood


def is_expired(stk: Stock, date: str):
    match stk.kind:
        case Food(mhd):
            print("lezter tag an dem nicht abgelaufen")
            print(Food(mhd), date)
        case Food.mhd:
            print("Hi")
        case NonFood():
            print("kann nicht ablaufen")


def get_expired(unit: list, date: str):
    abgelaufen = []
        

def buy():
    pass
Die Funktion sollte mit Folgender Datei getestet werden.

Code: Alles auswählen

from supermarket import Stock, Food, NonFood, get_expired, buy


def test_supermarket():
    # test Stock
    stocks: list[Stock] = [
        Stock("Chocolate", 12, 199, Food("2020-12-07"), ),
        Stock("Tooth Brush", 30, 299, NonFood())
    ]
    print(f"Stocks: {stocks}")
    assert stocks[0].name == "Chocolate"
    assert stocks[0].units == 12
    assert stocks[0].price_per_unit == 199
    #assert stocks[0].kind.expiration_date == "2020-12-07"

    assert stocks[1].name == "Tooth Brush"
    assert stocks[1].units == 30
    assert stocks[1].price_per_unit == 299

    # test get_expired
#    assert get_expired(stocks, "2020-12-05") == []
#    assert get_expired(stocks, "2020-12-09") == [stocks[0]]

    # test buy
#    stock: Stock = Stock("Chocolate", 12, 199, Food("2020-12-07"))
#    assert stock.units == 12
    #assert buy(stock, 5) == 5
#    assert stock.units == 7
#    assert buy(stock, 25) == 7
#    assert stock.units == 0


if __name__ == "__main__":
    test_supermarket()

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Freitag 25. November 2022, 23:34
von narpfel
Was ist denn deine konkrete Frage? Was macht der Code? Gibt es eine Fehlermeldung? Wenn nicht, was erwartest du und an welcher Stelle beginnt deine Erwartung vom tatsächlichen Verhalten des Codes abzuweichen? Kannst du für jeden der drei `case`s erklären, warum du ihn geschrieben hast?

Als zusätzliche Anmerkung: Deine Typannotationen sind fehlerhaft:

Code: Alles auswählen

$ mypy --strict supermarket.py
supermarket.py:22: error: Function is missing a return type annotation  [no-untyped-def]
supermarket.py:33: error: Function is missing a return type annotation  [no-untyped-def]
supermarket.py:33: error: Missing type parameters for generic type "list"  [type-arg]
supermarket.py:34: error: Need type annotation for "abgelaufen" (hint: "abgelaufen: List[<type>] = ...")  [var-annotated]
supermarket.py:37: error: Function is missing a return type annotation  [no-untyped-def]
supermarket.py:37: note: Use "-> None" if function does not return a value
Found 5 errors in 1 file (checked 1 source file)

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Samstag 26. November 2022, 00:49
von __blackjack__
Und die Typannotationen in der Testfunktion sind schlicht überflüssig. Das ist wie mit Kommentaren: Wenn die das offensichtliche aussagen, braucht man sie nicht. Bei einer *leeren* Liste kann es Sinn machen eine Typannotation zu schreiben, weil man an der Stelle sonst nicht weiss, was da rein kommen soll. Aber wenn nach dem ``=`` ein Ausdruck steht bei dem sonnenklar ist was der für einen Typ hat, macht es keinen Sinn das noch mal extra hin zu schreiben. Weder für den menschlichen Leser, noch für eine statische Typprüfung.

Das sollte IMHO auch nicht nur eine Testfunktion sein. Da werden zu viele und zu verschiedene Dinge für *eine* Funktion getestet.

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Samstag 26. November 2022, 14:02
von Sirius3
Intern Sollte man ein Datum immer vom Typ datetime.date speichern, dafür Strings zu benutzen ist umständlich und fehleranfällig. Benutze keine kryptischen Abkürzungen, wenn Du `stock` meinst, schreibe nicht `stk`. Und statt `mhd` `best_before_date`.

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Samstag 26. November 2022, 16:30
von kbr
Was für ein unschönes Beispiel, um Python zu lernen. Wenn die Klasse Stock geeignet implementiert wird, reduziert sich der Aufwand zum Test auf das Erreichen des Mindesthaltbarkeitsdatum auf:

Code: Alles auswählen

def is_expired(stock, date):
    return stock.is_food and stock.expiration < date
Dazu bedarf es weder Dataclasses, noch Pattern-Matching und auch auch keines Type-Pattern-Matching. Aber als Methode auf Stock wäre es sinnvoll.

Und inhaltlich ist die Aufgabenstellung auch falsch, denn das Mindesthaltbarkeitsdatum ist keineswegs gleichbedeutend mit dem Verfallsdatum von schnell verderblichen Waren. Insofern ist die Funktions-Bezeichnung 'is_expired' irreführend.

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Samstag 26. November 2022, 18:59
von __blackjack__
Ich würde sogar sagen, das `stock.is_food` ist hier irrelevant, weil es auch Haltbarkeits- oder Verfallsdaten bei Waren gibt die keine Nahrung sind. Also eher

Code: Alles auswählen

def is_expired(stock, date):
    return stock.expiration is not None and stock.expiration < date

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Samstag 26. November 2022, 19:55
von kbr
Ja, das hängt von der genaueren Spezifizierung von Stock ab. Es ist eben eine unschöne Aufgabe, da sie nicht eine elegante „pythonische“ Lösung wünscht, sondern das Gegenteil fordert.

Gute Übungen für dataclasses und type-pattern-matching zu finden ist noch nicht ganz einfach, da dies aus meiner Sicht zwei Lösungen sind, die sich gegenwärtig noch auf der Suche nach praktischen Problemen befinden.

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Sonntag 27. November 2022, 13:01
von DeaD_EyE
Was mich am meisten stört, dass mhd ein str ist und kein date Obkekt.
Der Vergleich müsste auch mit str funktionieren, wenn strikt YYYY-MM-DD eingehalten wird. Ich vergleiche aber lieber date-instanzen. Des Weiteren könnte man auch ein ungültiges Datum verwenden (2022-02-29).

Code: Alles auswählen

from dataclasses import dataclass
from datetime import datetime as DateTime


@dataclass
class Food:
    mhd: str


@dataclass
class NonFood:
    pass


@dataclass
class Stock:
    name: str
    units: int
    price_per_unit: int
    kind: Food | NonFood


def is_expired(stk: Stock, date: str) -> bool:
    # NonFood hat kein mhd, kann aber als stk.kind vorkommen
    # also muss das zuerst abgefragt werden
    match stk.kind:
        case NonFood():
            # ist immer False, da NonFood nicht ablaufen kann
            # (davon z.B. ausgeschlossen: Amiga 500)
            return False
        case Food(mhd):
            # das ist ziemlich doof
            # wieso werden die strings nicht direkt in date-Objekte
            # umgewandelt und verwendet?
            return (
                DateTime.strptime(date, "%Y-%m-%d").date()
                > DateTime.strptime(mhd, "%Y-%m-%d").date()
            )


def get_expired(unit: list, date: str) -> list[Stock]:
    return [article for article in unit if is_expired(article, date)]


def buy(article: Stock, amount: int) -> int:
    """
    Keine Ahnung was diese Funktion machen soll
    Die tests scheinen wohl die gekaufte Menge als int zu erwarten.
    """
    amount = min(article.units, amount)
    article.units -= amount
    return amount

Tests:

Code: Alles auswählen

from supermarket import Stock, Food, NonFood, get_expired, buy


def test_supermarket():
    # test Stock
    stocks: list[Stock] = [
        Stock("Chocolate", 12, 199, Food("2020-12-07"), ),
        Stock("Tooth Brush", 30, 299, NonFood())
    ]
    print(f"Stocks: {stocks}")
    assert stocks[0].name == "Chocolate"
    assert stocks[0].units == 12
    assert stocks[0].price_per_unit == 199
    #assert stocks[0].kind.expiration_date == "2020-12-07"

    assert stocks[1].name == "Tooth Brush"
    assert stocks[1].units == 30
    assert stocks[1].price_per_unit == 299

    # test get_expired
    assert get_expired(stocks, "2020-12-05") == []
    assert get_expired(stocks, "2020-12-09") == [stocks[0]]

    # test buy
    stock: Stock = Stock("Chocolate", 12, 199, Food("2020-12-07"))
    assert stock.units == 12
    assert buy(stock, 5) == 5
    assert stock.units == 7
    assert buy(stock, 25) == 7
    assert stock.units == 0


if __name__ == "__main__":
    test_supermarket()

PS: Die Tests sind schlecht.
PPS: Die NonFood dataclass hat keinerlei Funktion

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Montag 28. November 2022, 07:19
von ThomasL
voldemort_73 hat geschrieben: Freitag 25. November 2022, 19:35 Das Feld kind soll eine Instanz der Datenklassen Food oder NonFood
enthalten, definieren Sie es also als Union-Typ über die beiden Datenklassen.
Ist zwar schon länger her das ich mal in diese andere Sprache reingeschnuppert habe aber dieser Satz hier ist imho ein Indiz dafür, dass dies keine Übung für Python ist sondern eher für Java.
Oder irre ich mich?

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Montag 28. November 2022, 07:34
von noisefloor
Hallo,

nee, das Python typing-Modul kennt auch `Union`, siehe https://docs.python.org/3/library/typin ... ping.Union.

Im Kontext der Aufgabe macht das aber schon Sinn. Was ja nicht heißt, dass die Aufgabe im gesamten Sinn macht, siehe diverse vorangegangene Posts ;-)

Gruß, noisefloor

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Montag 28. November 2022, 10:26
von DeaD_EyE
https://peps.python.org/pep-0604/ ersetzt typing.Union.

Wenn man immer brav

Code: Alles auswählen

from __future__ import annotations
nutzt, kann man auch Features von typing nutzen, die noch nicht bei der entsprechenden Python-Version verfügbar sind.
Die PEP604 ist z.B. mit Python 3.10 eingeführt worden, kann aber mit Python 3.9 auch genutzt werden:

Code: Alles auswählen

from __future__ import annotations



def foo(x: int | float) -> complex:
    return complex(x, x)
    
    
foo("a")
Wenn man dann mypy ausführt, bekommt man folgende Meldung:

Code: Alles auswählen

ppp.py:9: error: Argument 1 to "foo" has incompatible type "str"; expected "Union[int, float]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
Wenn man den Code ausführt, bekommt man einen anderen Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/andre/ppp.py", line 9, in <module>
    foo("a")
  File "/home/andre/ppp.py", line 6, in foo
    return complex(x, x)
TypeError: complex() can't take second arg if first is a string
Die Typen sind nur ein Hinweis für die IDE (oder Menschen), werden aber nicht zur Laufzeit erzwungen.

Es gibt Frameworks, die typehints nutzen, um zur Laufzeit die Typen zu prüfen. pydantic macht das z.B..
FastAPI nutzt die typehints auch zur Konvertierung, aber auch um bei Funktionen an bestimmte Objekte zu kommen (Request).

Re: Python Aufgaben: Dataclasses, Pattern Matching

Verfasst: Montag 28. November 2022, 17:18
von ThomasL
Danke für die Ausführungen. Habe ich wieder etwas dazu gelernt. :-)