Seite 1 von 1

Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 10:12
von Tobbe94
Hallo liebe Mitglieder,

Ich lerne seit ein paar Wochen in einem Online-Kurs Python. Eine Übungsaufgabe dabei ist, ein kleines Programm für einen Gebrauchtwagenhändler zu erstellen, welches "einen neuen Wagen hinzufügen", "fahrzeug verkaufen", "Preis eines Fahrzeugs ändern", "sortiment anzeigen" und "programm beenden beherrschen soll.

Meine aktuellen Schwierigkeiten: Wie baue ich eine korrekte Endlos-Schleife für das Programm (habe es mit range provisorisch gelöst), Wie kann ich die Artikelnummer eineindeutig machen, sodass keine bereits vorhandene Artikelnummer doppelt in der Liste vorkommen kann und wie kann ich ein verkauftes Auto aus der Autoliste löschen? Ich weiß, dass ist sehr viel und für viele sehr einfach, aber ich programmiere erst seit 2 Wochen. Bin um jeden Rat oder Lösungsansatz dankbar!!!

Soweit bin ich aktuell: (darüber hinaus sollen wir eine Klasse "Auto" erstellen, in welcher für jede der oben genannten Auswahlmöglichkeiten Methoden oder Funktionen erstellt werden sollen. Das habe ich bereits in einem anderen File erstell, jedoch noch nicht lauffähig integrieren können...aber eins nach dem anderen :-) )


#Auswahlmöglichkeiten für den Kunden

auswahl = ["1: Neuen Wagen hinzufügen", "2: Fahrzeug verkaufen", "3: Preis eines Fahrzeugs ändern", "4: Sortiment anzeigen", "5: Programm beenden"]

#Modul "Autos" mit Artikelnummer, Marke, Modell, Farbe, Baujahr und Preis

auto_liste = [[1, "BMW", "1er", "Blau", 2018, 30000], [2, "MB", "A", "Schwarz", 2019, 40000], [3, "Opel", "Astra", "Schwarz", "2015", 10000]]


for i in range(1000):
print(auswahl)
eingabe = int(input("Was möchten Sie tun?: "))

if eingabe == 1:
print("Welches Auto soll hinzugefügt werden?")
artnr_neu = int(input("Artikelnummer: "))
#while artnr_neu in auto_liste[0]: <-- hier habe ich versucht die Artn. eineindeutig zu machen, hat nicht geklappt
#input("Diese Nummer ist bereits vergeben! ")
#exit()
marke_neu = str(input("Marke: "))
modell_neu = input("Modell: ")
farbe_neu = str(input("Farbe: "))
baujahr_neu = int(input("Baujahr: "))
preis_neu = float(input("Preis: "))
neues_auto = [marke_neu, modell_neu, farbe_neu, baujahr_neu, preis_neu]
auto_liste.append(neues_auto)
print(auto_liste)

elif eingabe == 2:
print(auto_liste)
verkauftes_auto = input("Welches Auto wurde verkauft?: ")
auto_liste.remove(verkauftes_auto)
print(auto_liste)


elif eingabe == 3:
input("Neuer Preis: ")

elif eingabe == 4:
print(auto_liste)

elif eingabe == 5:
print("Sie haben das Programm verlassen.")
exit()

else:
print("Diese Eingabe ist ungültig!")
continue

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 11:05
von Tobbe94
Als Anhang kann ich euch noch meine erstellte Klasse "Auto" zeigen, in welchem ich für das ändern des Preises eine Methode geschrieben habe und für den Rest eine Funktion (Die Daten sind gekapselt, weil das so in der Aufgabe stand):


class Auto:
"""
Erstellt das Objekt Auto für einen Autohändler.
"""
def __init__(self, artnr, marke, modell, farbe, baujahr, preis):
"""
Initialisiert ein neues Objekt Auto

Argumente:
* Artikelnummer (int): Artikelnummer (eindeutig)
* Marke (str) : Marke des Autos
* Modell (str): Modell des Autos
* Farbe (str): Farbe des Autos
* Baujahr (int): Baujahr des Autos
* Preis (float): Preis des Autos
"""

self.__artnr = artnr
self.__marke = marke
self.__modell = modell
self.__farbe = farbe
self.__baujahr = baujahr
self.__preis = preis


def get_artnr(self):
return self.__artnr

def get_marke(self):
return self.__marke

def get_modell(self):
return self.__modell

def get_farbe(self):
return self.__farbe

def get_baujahr(self):
return self.__baujahr

def get_preis(self):
return self.__preis

def set_preis(self, preis):
self.__preis = preis


auto_liste = []
auto_liste.append(Auto(1, "BMW", "1er", "Blau", 2018, 30000))
auto_liste.append(Auto(2, "Mercedes", "A", "Schwarz", 2019, 40000))
auto_liste.append(Auto(3, "Opel", "Astra", "Rot", 2015, 10000))

auto_liste[0].set_preis(30000)

print(auto_liste[0].get_preis())

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 11:59
von Sirius3
Eine Endlosschleife macht man mit `while True:`.
input liefert schon Strings, da ist ein str-Aufruf unnötig.
`exit` hat in einem sauberen Programm nichts verloren. Die Schleife kann per `break` verlassen werden.
`continue` sollte nicht verwendet werden und ist hier auch gar nicht nötig, da die Schleife eh normal weitergeht.
Wenn Du keine doppelten Nummern vergeben willst, dann mußt Du halt alle Nummern durchsuchen, ob es die eingegebene schon gibt.

Zur Klasse: vergiss gleich wieder, dass es doppelte Unterstriche gibt. Da alle Attribute eh durch get_xxx und set_xxx öffentlich ist, kannst Du sie gleich öffentlich lassen und so wird die Klasse deutlich kürzer. Benutze keine Abkürzungen. Wenn Du Artistennummer meinst, schreibe nicht artnr.
Datentypen haben in Variablennamen nichts verloren.

Code: Alles auswählen

class Auto:
    """ Objekt Auto für einen Autohändler. """
    def __init__(self, artikelnummer, marke, modell, farbe, baujahr, preis):
        """
        Initialisiert ein neues Objekt Auto
        
        Argumente:
        * Artikelnummer (int): Artikelnummer (eindeutig)
        * Marke (str) : Marke des Autos
        * Modell (str): Modell des Autos
        * Farbe (str): Farbe des Autos
        * Baujahr (int): Baujahr des Autos
        * Preis (float): Preis des Autos
        """
        self.artikelnummer = artikelnummer
        self.marke = marke
        self.modell = modell
        self.farbe = farbe
        self.baujahr = baujahr
        self.preis = preis

autos = [
    Auto(1, "BMW", "1er", "Blau", 2018, 30000),
    Auto(2, "Mercedes", "A", "Schwarz", 2019, 40000),
    Auto(3, "Opel", "Astra", "Rot", 2015, 10000),
]
autos[0].preis = 29000
print(autos[0].preis)

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:07
von __blackjack__
@Tobbe94: Ich weiss nicht wie der Kurs sonst so ist, aber diese Übungsaufgabe gab es hier vor einiger Zeit schon mal und anscheinend soll so etwas wie Preis ändern, inklusive Interaktion mit dem Nutzer, in eine Methode auf dem `Auto` landen. Das ist *falsch*! Alle Punkte sind einfach Funktionen. Das `Auto` ist eine einfache Datenklasse. Bestenfalls eine Klasse für die Geschäftslogik. Da gehört keine Interaktion mit dem Benutzer rein. Also weder `input()` noch `print()` hat in der `Auto`-Klasse etwas zu suchen.

Wenn der Kurs auf doppelte führende Unterstriche besteht ist das kein Python-Kurs, und wenn Benutzerinteraktion in der `Auto`-Klasse stattfinden soll, dann ist kein guter Kurs um objektorientierte Programmierung zu lernen.

Der Klassendocstring und der von der `__init__()` sind mindestens mal etwas irreführend und enthalten auch Informationen die da nicht drin sein müssen. Das ``class Auto`` ein Objekt `Auto` erzeugt muss man nicht dokumentieren, das sollte klar sein. Und auch das die `__init__()` ein Objekt initialisiert ist superbanal und muss da nicht stehen.

Da die `__init__()` letztlich einfach nur die Attribute setzt die den gleichen Namen haben wie die Argumente würde man auch eher die Attribute im Klassen-Docstring dokumentieren und den Docstring bei der `__init__()` weglassen. In Sphinx wird per Default bei der Autodoc-Erweiterung nur der Docstring der Klasse in die Dokumentation übernommen. Wenn man den der `__init__()` haben möchte muss man das entweder explizit bei jeder Klasse sagen, oder Konfigurieren das nur der Docstring der `__init__()` verwendet werden soll, oder beide nacheinander gesetzt werden.

Edit: Und das diese supersimple Klasse in ein eigenes Modul soll riecht auch komisch. Das ist in Sprachen wie Java üblich aber in Python ist eine Datei ein Modul und ein Modul ist dazu da um zusammengehörige Funktionen und Klassen zu bündeln. Wenn man in jedes Modul nur eine Klasse steckt, hat man eine unnötige sinnfreie Ebene in die Modulhierarchie eingefügt und Module letztendlich sinnentstellt.

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:36
von Tobbe94
Sirius3 hat geschrieben: Donnerstag 24. September 2020, 11:59 Eine Endlosschleife macht man mit `while True:`.
input liefert schon Strings, da ist ein str-Aufruf unnötig.
`exit` hat in einem sauberen Programm nichts verloren. Die Schleife kann per `break` verlassen werden.
`continue` sollte nicht verwendet werden und ist hier auch gar nicht nötig, da die Schleife eh normal weitergeht.
Wenn Du keine doppelten Nummern vergeben willst, dann mußt Du halt alle Nummern durchsuchen, ob es die eingegebene schon gibt.

Zur Klasse: vergiss gleich wieder, dass es doppelte Unterstriche gibt. Da alle Attribute eh durch get_xxx und set_xxx öffentlich ist, kannst Du sie gleich öffentlich lassen und so wird die Klasse deutlich kürzer. Benutze keine Abkürzungen. Wenn Du Artistennummer meinst, schreibe nicht artnr.
Datentypen haben in Variablennamen nichts verloren.

Code: Alles auswählen

class Auto:
    """ Objekt Auto für einen Autohändler. """
    def __init__(self, artikelnummer, marke, modell, farbe, baujahr, preis):
        """
        Initialisiert ein neues Objekt Auto
        
        Argumente:
        * Artikelnummer (int): Artikelnummer (eindeutig)
        * Marke (str) : Marke des Autos
        * Modell (str): Modell des Autos
        * Farbe (str): Farbe des Autos
        * Baujahr (int): Baujahr des Autos
        * Preis (float): Preis des Autos
        """
        self.artikelnummer = artikelnummer
        self.marke = marke
        self.modell = modell
        self.farbe = farbe
        self.baujahr = baujahr
        self.preis = preis

autos = [
    Auto(1, "BMW", "1er", "Blau", 2018, 30000),
    Auto(2, "Mercedes", "A", "Schwarz", 2019, 40000),
    Auto(3, "Opel", "Astra", "Rot", 2015, 10000),
]
autos[0].preis = 29000
print(autos[0].preis)
Vielen Dank für deine schnelle und hilfreiche Anwort. Habe die Endlosschleife nun wie folg geschrieben:

while i = True:

(...Programm)

elif eingabe:
i = False
break

es scheint so zu funktionieren! Passt das so, oder ist das wieder nicht optimal?

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:40
von Tobbe94
__blackjack__ hat geschrieben: Donnerstag 24. September 2020, 13:07 @Tobbe94: Ich weiss nicht wie der Kurs sonst so ist, aber diese Übungsaufgabe gab es hier vor einiger Zeit schon mal und anscheinend soll so etwas wie Preis ändern, inklusive Interaktion mit dem Nutzer, in eine Methode auf dem `Auto` landen. Das ist *falsch*! Alle Punkte sind einfach Funktionen. Das `Auto` ist eine einfache Datenklasse. Bestenfalls eine Klasse für die Geschäftslogik. Da gehört keine Interaktion mit dem Benutzer rein. Also weder `input()` noch `print()` hat in der `Auto`-Klasse etwas zu suchen.

Wenn der Kurs auf doppelte führende Unterstriche besteht ist das kein Python-Kurs, und wenn Benutzerinteraktion in der `Auto`-Klasse stattfinden soll, dann ist kein guter Kurs um objektorientierte Programmierung zu lernen.

Der Klassendocstring und der von der `__init__()` sind mindestens mal etwas irreführend und enthalten auch Informationen die da nicht drin sein müssen. Das ``class Auto`` ein Objekt `Auto` erzeugt muss man nicht dokumentieren, das sollte klar sein. Und auch das die `__init__()` ein Objekt initialisiert ist superbanal und muss da nicht stehen.

Da die `__init__()` letztlich einfach nur die Attribute setzt die den gleichen Namen haben wie die Argumente würde man auch eher die Attribute im Klassen-Docstring dokumentieren und den Docstring bei der `__init__()` weglassen. In Sphinx wird per Default bei der Autodoc-Erweiterung nur der Docstring der Klasse in die Dokumentation übernommen. Wenn man den der `__init__()` haben möchte muss man das entweder explizit bei jeder Klasse sagen, oder Konfigurieren das nur der Docstring der `__init__()` verwendet werden soll, oder beide nacheinander gesetzt werden.

Edit: Und das diese supersimple Klasse in ein eigenes Modul soll riecht auch komisch. Das ist in Sprachen wie Java üblich aber in Python ist eine Datei ein Modul und ein Modul ist dazu da um zusammengehörige Funktionen und Klassen zu bündeln. Wenn man in jedes Modul nur eine Klasse steckt, hat man eine unnötige sinnfreie Ebene in die Modulhierarchie eingefügt und Module letztendlich sinnentstellt.
Hallo! Vielen Dank für deine Antwort! Ich weiß natürlich nicht ob ich die Aufgabenbeschreibung richitg verstanden habe, deshalb hier ist die Aufgabe mal:

1) ​Schreibe ein Programm für einen Gebrauchtwagenhändler. Dieses soll aus einer  Endlosschleife bestehen, die den Anwender fragt, welche Aktion er durchführen will.  Dabei soll er folgende Optionen haben: 
- Neuen Wagen hinzufügen  
- Fahrzeug verkaufen  
- Preis eines Fahrzeugs ändern  
- Sortiment anzeigen   - Programm beenden   
2) ​Die einzelnen Autos sollen in einem Objekt gespeichert werden. Erstelle hierfür in  einem neuen Modul die Klasse Auto. Sie soll die Attribute Artikelnummer, Marke,  Modell, Farbe, Baujahr und Preis enthalten. Diese Daten sollen gekapselt sein.   
3) ​Gestalte daraufhin für jede der oben genannten Aktionen eine passende Methode  oder Funktion, in die du Befehle/Programmteile auslagerst.   Wenn sie ein vorhandenes Objekt verändert, ist eine Methode die richtige Wahl. In  allen anderen Fällen bietet es sich an, eine Funktion zu verwenden.    
4) ​Das gesamte Sortiment soll in einer Liste abgespeichert werden. Diese enthält  Objekte vom Typ Auto. Es ist sinnvoll, wenn der Zugriff auf ein Objekt immer über die  Artikelnummer stattfindet. Dazu ist es wichtig, darauf zu achten, dass jede Nummer nur  ein einziges Mal vergeben wird. 

die Unterstriche benötige ich ja für die Datenkapselung der Attribute der Klasse Auto oder? Im Kurs hieß es, es sei bei sehr einfachen Programmen oftmals nicht notwendig aber für größere Programme bezüglich Sicherheit sollte man davon wissen.

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:43
von Jankie
Warum hast du in das While jetzt noch ein i eingebaut?

vielleicht hilft dir das zur Veranschaulichung:

Code: Alles auswählen

while True:
    eingabe = input("Gib irgendwas ein (zum Abbrechen 'q' eingeben): ")
    if eingabe == 'q':
        break

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:52
von Tobbe94
Jankie hat geschrieben: Donnerstag 24. September 2020, 13:43 Warum hast du in das While jetzt noch ein i eingebaut?

vielleicht hilft dir das zur Veranschaulichung:

Code: Alles auswählen

while True:
    eingabe = input("Gib irgendwas ein (zum Abbrechen 'q' eingeben): ")
    if eingabe == 'q':
        break
Im Kapitel mit while haben wir das immer mit dem i so gemacht. Wusste nicht, dass das so auch geht. Aber so ists natürlich einfacher, danke! Wieder was gelernt :)

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 13:54
von Tobbe94
Kann mir eventuell noch jemand kurz zeigen, wie ich bei meinem Programm durch einen User-Input eine Liste (Auto) aus meiner Auto-Liste (Autosortiment) löschen kann? Habe versucht den Input-Wert mit einer Variablen zu versehen (verkauftes_auto), welcher dann mit remove aus der Liste entfernt werden soll. Allerdings kommt immer ein ValueError: list.remove(x): x not in list?! Hab da schon stundenlang rumprobiert aber nichts klappt :D
Glaub, das ist das einzige was ich noch nicht kapiere, neben der Preisänderung im Hauptprogramm

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 14:03
von garreth
Tobbe94 hat geschrieben: Donnerstag 24. September 2020, 13:40 die Unterstriche benötige ich ja für die Datenkapselung der Attribute der Klasse Auto oder? Im Kurs hieß es, es sei bei sehr einfachen Programmen oftmals nicht notwendig aber für größere Programme bezüglich Sicherheit sollte man davon wissen.
Ja, das mag auf andere Programmiersprachen durchaus zutreffen. In Python gibt es aber sowas wie private Funktionen nicht.

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 14:05
von __blackjack__
@Tobbe94: Du musst nicht immer ganze Beiträge als Zitat einfügen. Wenn Du Dich auf den letzten Beitrag von jemandem beziehst, dann ist ja klar was gemeint ist, denn die kann man ja etwas weiter oben nachlesen.

Ein Zitat macht eigentlich nur Sinn wenn man sich auf eine ganz konkrete Formulierung bezieht, die man nicht im eigenen Beitrag einfach kurz umschreiben oder drauf hinweisen kann.

Du brauchst keine doppelten führenden Unterstriche sondern höchstens einfache Unterstriche. Das ist in Python die Konvention für Attribute die nicht zur öffentlichen API gehören. Doppelte Unterstriche sind dazu da, dass es bei Mehrfachvererbung oder tiefen Klassenhierarchien keine Namenskollisionen gibt. Da man beides kaum bis gar nicht macht, ist es auch ganz selten, dass man das mal braucht. An die Werte zu den Attributen kommt man von aussen in jedem Fall herankommen.

Letztlich gibt es keinen Zugriffsschutz in Python und man sollte den auch nicht versuchen irgendwie rein zu hacken. Oder Begriffe wie ``private`` und ``protected`` irgendwie auf Python abbilden zu wollen. Wenn man so etwas haben will/braucht, sollte man eine Programmiersprache verwenden die das bietet.

Zum ``while i == True:``: Man macht keine Vergleiche mit literalen Wahrheitswerten. Bei dem Vergleich kommt doch nur wieder ein Wahrheitswert bei heraus. Entweder der, den man sowieso schon hatte; dann kann man den auch gleich nehmen. Oder das Gegenteil davon; dafür gibt es ``not``. Also in diesem Fall einfach ``while i:``. Wobei `i` kein guter Name für einen Wahrheitswert ist.

Um ein Auto mit `remove()` aus der Liste zu entfernen, müsstest Du das Auto-Objekt, das entfernt werden soll, an `remove()` übergeben. Und das kann ja kein Eingabewert von `input()` sein, denn das sind Zeichenketten. In der Liste sind aber `Auto`-Objekte. Und ein `Auto`-Objekt ist nie gleich einer Zeichenkette. Also sofern man die Vergleichsoperationen beim `Auto` nicht auf perverse Weise überlädt zumindest.

Du hast, oder Du brauchst, ja irgendwo eine Funktion um zu einer Artikelnummer das dazugehörige Auto-Objekt aus der Liste zu holen. Oder zumindest den Index. Das kannst Du dann entweder mit `remove()` oder `pop()` verwenden um das Auto aus der Liste zu entfernen.

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 14:19
von __blackjack__
Ich wusste das mir das bekannt vorkommt, und jetzt weiss ich auch wie ich darauf komme das die Benutzerinteraktion scheinbar in die `Auto`-Klasse soll. Das sind Beispiele aus einem Buch, und konkret das `Auto` aus dem Buch ist dieses furchtbare Stück Code: viewtopic.php?f=1&t=46546#p352813

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 14:25
von Tobbe94
Könnte ich das nicht mit einem int(input()) umgehen? Mein Idee war, dass der User Anhand der Liste und der Artnr. [ [1,... ], [2,... ], [3, ] ] das zu löschende Auto erkennt. Dann soll der Eingabewert durch die Variable in remove() gepackt werden und den x. Listeneintrag rauslöschen. Beispielsweise User-Eingabe 2 steht für Artnr. 2 und somit wird der zweite Eintrag der Liste gelöscht.
Ist das Schwachsinn oder geht das? Haha ich stelle glaub ich sehr banale fragen aber ich tu mir noch schwer, das alles zu verstehen.

Zum zweiten: ja, scheint irgendwie ein bekanntes Übungsbeispiel zu sein. Ich finde bestimmt irgendwo eine Lösung im Internet, aber das bringt mich persönlich nicht so viel weiter, leider :(

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 14:33
von Sirius3
Tobbe94 hat geschrieben: Donnerstag 24. September 2020, 14:25Zum zweiten: ja, scheint irgendwie ein bekanntes Übungsbeispiel zu sein. Ich finde bestimmt irgendwo eine Lösung im Internet, aber das bringt mich persönlich nicht so viel weiter, leider :(
Vor allem weil das Beispiel so grottenschlecht ist.

Du mußt beim Programmieren exakt beschreiben, was Du machen willst.
Wenn der Nutzer die Artikelnummer eingibt, dann mußt Du erst den Listeneintrag mit der passenden Artikelnummer suchen.
Wenn Du dann dieses Auto gefunden hast, kannst Du es auch mit remove entfernen.

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 15:50
von Tobbe94
Hmm...okay? :D werde dann einfach mal weiter rumprobieren. Aber danke schon mal

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Donnerstag 24. September 2020, 15:52
von Tobbe94
Habe noch 2 Wochen Zeit, bis ein lauffähiges Programm abgegeben werden muss. Hoffe ich kriege das hin :)

Re: Hilfe bei einem Programmteil (Listenfunktionen)

Verfasst: Freitag 9. Oktober 2020, 11:28
von __blackjack__
Da die zwei Wochen um sind hier mal eine Python-Lösung zu der Aufgabe. Ist keine Musterlösung weil die Aufgabe teilweise ”unpythonisches” verlangt, das hier aber ein Python-Forum ist und kein „wie verkleide ich ein Java-Programm ☕️ als Python 🐍“-Forum. Ist zwar gerade der 🎃Halloween-Monat🎃, aber da müsste das umgekehrt laufen: man verkleidet sich ja als etwas gruseliges 🧟, was in dieser Kombo Java wäre. 😜

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial

from attr import attrib, attrs


def ask_something(type_description, convert_function, prompt):
    while True:
        try:
            return convert_function(input(prompt))
        except ValueError:
            print(f"Bitte {type_description} eingeben!")


ask_integer = partial(ask_something, "eine ganze Zahl", int)
ask_float = partial(ask_something, "eine Zahl", float)


@attrs(frozen=True)
class MenuItem:
    text = attrib()
    action = attrib(default=lambda cars: print("Noch nicht implementiert!"))


@attrs
class Car:
    article_number = attrib()
    brand = attrib()
    model = attrib()
    color = attrib()
    year_built = attrib()
    price = attrib()

    def __str__(self):
        return (
            f"{self.article_number}: {self.year_built}er {self.brand}"
            f" {self.model} ({self.color}) für {self.price}€"
        )


def get_car(cars, article_number):
    return next(
        (car for car in cars if car.article_number == article_number), None
    )


def ask_car(cars, prompt):
    article_number = ask_integer(prompt)
    return (article_number, get_car(cars, article_number))


def add_car(cars):
    article_number, car = ask_car(cars, "Neue Artikelnummer: ")
    if car:
        print(f"Artikelnummer {article_number} existiert bereits!")
    else:
        cars.append(
            Car(
                article_number,
                input("Marke: "),
                input("Modell: "),
                input("Farbe: "),
                ask_integer("Baujahr: "),
                ask_float("Preis: "),
            )
        )


def sell_car(cars):
    article_number, car = ask_car(cars, "Artikelnummer für Verkauf: ")
    if car:
        cars.remove(car)
    else:
        print(f"Artikelnummer {article_number} existiert nicht!")


def change_car_price(cars):
    article_number, car = ask_car(cars, "Artikelnummer für Preisänderung: ")
    if car:
        print(car)
        car.price = ask_float("Neuer Preis: ")
    else:
        print(f"Artikelnummer {article_number} existiert nicht!")


def show_cars(cars):
    if cars:
        for car in cars:
            print(car)
    else:
        print("Es sind keine Autos im Bestand!")


def main():
    cars = [
        Car(1, "BMW", "1er", "Blau", 2018, 30000),
        Car(2, "Mercedes", "A", "Schwarz", 2019, 40000),
        Car(3, "Opel", "Astra", "Rot", 2015, 10000),
    ]
    menu_items = [
        MenuItem("Wagen hinzufügen.", add_car),
        MenuItem("Fahrzeug verkaufen.", sell_car),
        MenuItem("Preis eines Fahrzeugs ändern.", change_car_price),
        MenuItem("Sortiment anzeigen.", show_cars),
    ]
    while True:
        print()
        for number, menu_item in enumerate(menu_items, 1):
            print(f"{number}) {menu_item.text}")
        print("0) Programm beenden.")

        choice = ask_integer(f"Auswahl (0-{len(menu_items)}): ")
        if choice == 0:
            break

        if 1 <= choice <= len(menu_items):
            menu_items[choice - 1].action(cars)
        else:
            print("Keine gültige Auswahl!")

        input("Eingabetaste um zum Menü zurück zu kehren.")


if __name__ == "__main__":
    main()