Warum ist die Liste leer ?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

Hallo zusammen :-)
ich habe ein Problem, das mich ziemlich nervt. Ich lade (pickle) aus einer Datei eine Liste mit dem Bezeichner 'charts'. Mittels *print(charts)* wird mir der Inhalt auch angezeigt. Wenn ich dann im Auswahlmenu 'a' für *(A)nzeige der Chartliste* eingebe, ist diese Liste 'charts' auf einmal leer *print(charts)* => [].
Ich hänge euch mal das Skript an, mit der Bitte, nur 'a' in die Auswahlliste einzugeben, beim Rest gibt's Fehler, da ich die ganze Zeit am Probieren bin, und nicht wieder alles "zurechtgezogen" habe. Ich weiß auch, dass einiges an Code deswegen unnötig ist.
Was mache ich falsch. Ich wollte euch später sowieso noch Fragen über den Programmierstil stellen, ihr könnt aber trotzdem jetzt schon darauf eingehen, da ich mir nichts Falsches angewöhnen möchte.

Code: Alles auswählen

##!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Autor:  Perlchamp
# Datum: 20.02.2019
# Dateiname: chartsmanager.py
# Zweck/Inhalt: Verwalten einer Chartliste

#TO DO
"""
Stimmabgabe
"""

#IMPORT
import pickle

#FUNKTIONDEFINITIONEN
def auswahl_treffen(charts) :
    import pickle
    print("-" * 30)
    print("MENUE".center(30))
    print("-" * 30)
    print("(A)nzeige der Chartliste")
    print("(C)hartliste erweitern")
    print("(L)öschen von Einträgen in der Chartliste")
    print("(M)odifizieren von Einträgen in der Chartliste")
    print("(E)nde")
    print("-" * 30)
    print()
    while True :
        auswahl = input("Treffe deine Auswahl :  ")
        if auswahl not in "eE" :
            if auswahl in "aA" :
                charts_laden(charts)
                charts_anzeigen(charts)
            elif auswahl in "cC" :
                charts_laden()
                charts_eingeben()
                auswahl_treffen()
            elif auswahl in "lL" :
                charts_laden()
                charts_loeschen()
                auswahl_treffen()
            elif auswahl in "mM" :
                charts_laden()
                charts_modifizieren()
                auswahl_treffen()
            else :
                continue
        else :
            charts_beenden()
            break
            
def charts_laden(charts) :
    try :
        with open("data/charts.dmp", "rb") as f :
            charts = pickle.load(f)
##        print("dummy : ", dummy)
##        charts = dummy[:]
        print("charts : ", charts)
##            print(charts)
        return charts
    except :
        print("leere Liste !")
        charts = []                   # charts.dmp ist leer oder nicht vorhanden
##       return charts

def charts_loeschen() :
    delete = " "
    while delete :
        print("Löschen")
        charts_anzeigen()
        print()
        delete = input("Gebe die Platznummer des zu löschenden Eintrag ein :  ")
##        print(charts)
        if delete :
            del charts[int(delete) - 1]
            charts_speichern()
##        else :
##            charts_anzeigen(charts)                  
    
def charts_eingeben() :
    print("Abbrechen der Eingaben mit Enter (=> keine Eingabe eines Titels) !")
    interpret = " "
    while interpret :
        interpret = ""
        titel = input("Titel : ")
        while titel :
            interpret = input("Interpret : ")
            if interpret :
                charts += [[0, titel, interpret]]
##                print(charts)
            titel = ""
    print()
    if len(charts) > 0 :
        print("Vielen Dank. Die Liste wird gespeichert !")
        charts_speichern()
    else :
        print("In die Liste wurde nichts eingetragen !")

def charts_speichern() :
    try :
        with open("data/charts.dmp", "wb") as f :
            pickle.dump(charts, f)
    except :
        print("Die Liste konnte nicht gespeichert werden")
        with open("data/charts.txt", "w") as backup :
            backup.write(charts)

def charts_anzeigen(charts) :
    print("-" *30)
    print("Die Chartliste".center(30))
    print("-" *30)
##    print("len_charts : ", len(charts))
    print("charts : ", charts)
    if len(charts) > 0 :
##        print("charts : ", charts)
        for i in range(len(charts)) :
            eintrag = charts[i]
            print("Platz", i+1, ": ", eintrag[1], "\t von : ", eintrag[2], "\t\tStimmen : ".rjust(30), eintrag[0])
##        auswahl_treffen(charts)
##    else :
##        auswahl_treffen(charts)
        
def charts_beenden() :
    print ("Programm schliessen mit <Enter>")
    input()                                                           # funktioniert nicht immer !

def main() :
    auswahl_treffen(charts)
    
#KONFIGURATION
charts = []

#ANWEISUNGEN
if __name__ == "__main__" :
    main()

#ENDE
##if(__name__ == "__main__"):
##    print ("Programm schliessen mit <Enter>")
##    input()
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

hier mal der Inhalt der binären Datei (mit Notepad geöffnet). Ich weiß also nicht, ob ihr das einfach kopieren könnt ...

Code: Alles auswählen

€]q (]q(K X
   enter sandmanqX	   Metallicaqe]q(K X   stairway to heavenqX   Led Zeppelinqe]q(K X
   can't you seeqX   The Marshall Tucker Bandq	ee.
... Versuch macht kluch ;-)
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: Die Liste ist leer weil die geladenen Charts zwar von `charts_laden()` an den Aufrufer zurückgegeben werden, aber an keiner Stelle etwas mit diesem Rückgabewert gemacht wird. Warum der Funktion der aktuelle Wert der Liste übergeben wird, ist mir übrigens nicht so ganz klar, denn die Funktion macht damit ja nichts. Sollte sie auch gar nicht. Und jeder mögliche Programmfluss durch die Funktion muss zur Rückgabe einer Chartsliste führen.

Die komplett gross geschriebenen Kommentare auf Modulebene sind überflüssig bis unsinnig. Ein Kommentar soll dem Leser einen Mehrwert gegenüber den Code geben. Vor den Importen einen ``#IMPORT``-Kommentar zu schreiben, macht das nicht. Jemand der bei ``import …`` nicht versteht das dort importiert wird, dem wird auch ein Kommentar nicht helfen der sagt das dort importiert wird.

Das gleiche gilt für Funktionsdefinitionen. Und ``#KONFIGURATION`` und ``#ANWESIUNGEN`` ist einfach nur verwirrend weil bei erstem gar keine Konfiguration folgt und letzteres eigentlich überall stehen könnte, denn so ein Modul besteht ja komplett aus einer Folge von Anweisungen.

Literale Zeichenketten sind keine Kommentare. Durch die Position von dem Stimmabgabe-”Kommentar” ist das sogar ein Docstring, in diesem Fall die Dokumentation für das gesamte Modul.

Für Autor und Datum kann man den speziellen Namen `__author__` und `__date__` auf Modulebene etwas zuweisen. Das wird von Dokumentationswerkzeugen wie `pydoc` oder Sphinx berücksichtigt. Und der Kommmentar für Zweck/Inhalt wäre besser in der ersten Zeile des Docstrings für das Modul aufgehoben.

Die She-Bang-Zeile fängt mit nur einem ``#`` an.

Wenn der Anfang der Datei so aussieht:

Code: Alles auswählen

#!/usr/bin/env python3
"""Verwalten einer Chartliste."""

__author__ = 'Perlchamp'
__date__ = '20.02.2019'
Bekommt man mit `pydoc`/`help()` folgende Ausgabe für das Modul:

Code: Alles auswählen

$ python3 -m pydoc chartsmanager
Help on module chartsmanager:

NAME
    chartsmanager - Verwalten einer Chartliste.

FUNCTIONS
    auswahl_treffen(charts)
    
    charts_anzeigen(charts)
    
    charts_beenden()
    
    charts_eingeben()
    
    charts_laden()
    
    charts_loeschen()
    
    charts_speichern()
    
    main()

DATA
    charts = []

DATE
    20.02.2019

AUTHOR
    Perlchamp

FILE
    /home/bj/chartsmanager.py
Da fällt `charts` auf: Das ist eine globale Variable und das sollte nicht sein. Diese Liste hat nichts auf Modulebene zu suchen.

Importe sollten alle am Anfang des Moduls stehen und nicht in Funktionen versteckt. Zumal der zweite Import von `pickle` gar keinen Effekt hat.

Der rekursive Aufruf von `auswahl_treffen()` ist falsch. Funktionsaufrufe sind nicht einfach Sprünge zu benannten Code-Abschnitten. Der Code muss auch berücksichtigten, das ein Funktionsaufruf zum Aufrufer zurückkehrt. Rekursion ist kein Ersatz für einfache Schleifen, sondern sollte wirklich nur verwendet werden, wenn dort ein tatsächlich rekursives Problem gelöst wird. Tatsächlich *falsch* ist das in Python weil es irgendwann zum Programmabbruch führt, da nicht beliebig oft rekursiv aufgerufen werden kann/darf. Das gilt so ziemlich generell für alle Programmiersprachen die keine „tail call optimization“ garantieren.

Dieses Programmfenster offenhalten mit `input()` ist nervig für Leute die Kommandozeilenprogramme so starten wie man die starten sollte: in der Kommandozeile. Und falls eine Ausnahme auftritt funktioniert das nicht, also möchte man das *wirklich* in einer Kommandozeile starten und nicht per Doppelklick.

Irgendwie erscheint mir `auswahl_treffen()` unnötig zu sein. Oder `main()`. Eins von beiden kann man sich definitiv sparen.

Nackte ``except``-Anweisungen ohne konkrete Ausnahmen die man dort erwartet sind in aller Regel eine ganz schlechte Idee weil die *alle* Ausnahmen behandeln, auch solche die man gar nicht erwartet. Da wird die Fehlersuche dann zum Abenteuer wenn man beispielsweise so tut als sei ein vertipper in einem Namen oder ein `MemoryError` das gleiche als wenn eine Datei nicht gefunden werden konnte oder unerwarteten Inhalt hat.

Daten und Code sollten nicht wiederholt werden. Das verletzt das DRY-Prinzip (Don't Repeat Yourself) und macht Programme schlechter wartbar und damit fehleranfälliger. Man muss bei Änderungen immer alle Kopien ändern und darauf achten das man a) jede Kopie findet und ändert und b) alle Änderungen gleich sind.

So etwas wie Pfade/Dateinamen definiert man deshalb als Konstanten *einmal* und kann sie dann an einer Stelle leicht ändern.

Wenn man die Eingabe im Hauptmenü in Kleinbuchstaben wandelt, kann man sich sparen beim Test immer den Gross- und Kleinbuchstaben anzugeben.

Das `e` anders alls alle anderen Eingaben behandelt wird ist komisch. Also klar, das ist die einzige Option die das Programm beendet, aber dafür braucht man keine zusätzliche Einrückebene.

Die Anfangs leere Liste die an `charts` zugewiesen wird, wird nirgends verwendet, diese Zuweisung kann man sich also sparen. Und da die Charts bei allen Menüpunkten (ausser Exit) geladen werden, kann man das *einmal* an den Anfang der Hauptschleife schreiben und nicht in (fast) jeden Zweig vom Menü.

Variablen vor ``while``-Schleifen mit komischen Dummywerten zu belegen damit die Schleife läuft, ist unschön. In den meisten Fällen will man da einfach eine ”Endlosschleife” die an der entsprechenden Stelle mit ``break`` verlassen wird.

`charts_loeschen()` und `charts_eingeben()` sind irreführende Namen weil die Funktionen sich auf einzelne Einträge beziehen und nicht auf die Charts als ganzes.

``+=`` bei Listen ist die etwas unleserliche Art die `extend()`-Methode aufzurufen. Und die ist hier nicht das richtige Mittel. Du verpackst grundsätzlich immer nur einen einzigen Eintrag in eine zusätzliche unnötige Liste um diesen *einen* Eintrag an die Liste effektiv gesehen *anzuhängen*. Dafür ist die `append()`-Methode da.

Listen enthalten üblicherweise an jedem Index Elemente die die gleiche Bedeutung haben. Das ist hier nicht der Fall. Hier hat jeder Index eine spezielle Bedeutung. Dafür nimmt man normalerweise Tupel.

In `charts_anzeigen()` kann man sich als erstes mal das ``if`` sparen. Das hat keinen Einfluss auf das Programmverhalten.

Dann ist ``for i in range(len(sequence)):`` in Python ein „anti pattern“. Man kann in Python *direkt* über die Elemente von Sequenzwerten iterieren, ohne den Umweg über einen Laufindex. Falls man *zusätzlich* zu den Elementen der Sequenz eine laufende Zahl benötigt, gibt es die `enumerate()`-Funktion.

Die vielen Argumente beim `print()`-Aufruf sind etwas unübersichtlich. Da würde man eher Zeichenkettenformatierung, oder ab Python 3.6 eine f-Zeichenkette verwenden.

Anstelle der ”magischen” Indexwerte für den Zugriff auf den `eintrag` wäre der Code lesbarer wenn man den Inhalt in der Schleife auf aussagekräftige Namen verteilt. Und falls das mehr als drei Elemente werden sollten, wäre es angebracht über ein `collections.namedtuple()` oder eine eigene Klasse nachzudenken.

Ungetesteter Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
"""Verwalten einer Chartliste."""

__author__ = 'Perlchamp'
__date__ = '20.02.2019'

import pickle

CHARTS_FILENAME = 'data/charts.dmp'
MENU_WIDTH = 30


def charts_laden():
    try:
        with open(CHARTS_FILENAME, 'rb') as file:
            charts = pickle.load(file)
    except (EOFError, OSError):  # Datei ist leer oder nicht vorhanden.
        print('leere Liste !')
        charts = list()

    print('charts:', charts)
    return charts


def charteintraege_loeschen(charts):
    while True:
        print('Löschen')
        charts_anzeigen(charts)
        print()
        answer = input('Gebe die Platznummer des zu löschenden Eintrag ein: ')
        if not answer:
            break
        #
        # TODO Fehleingaben berücksichtigen.
        # 
        del charts[int(answer) - 1]
        charts_speichern(charts)

    
def charteintraege_eingeben(charts):
    print('Abbrechen der Eingaben mit Enter (=> keine Eingabe eines Titels)!')
    while True:
        titel = input('Titel: ')
        if not titel:
            break
        interpret = input('Interpret: ')
        if not interpret:
            break
        charts.append((0, titel, interpret))

    print()
    if charts:
        print('Vielen Dank. Die Liste wird gespeichert!')
        charts_speichern(charts)
    else:
        print('In die Liste wurde nichts eingetragen!')


def charts_speichern(charts):
    try:
        with open(CHARTS_FILENAME, 'wb') as file:
            pickle.dump(charts, file)
    except OSError:
        print('Die Liste konnte nicht gespeichert werden!')


def charts_anzeigen(charts):
    print('-' * MENU_WIDTH)
    print('Die Chartliste'.center(MENU_WIDTH))
    print('-' * MENU_WIDTH)
    for platz, (stimmenanzahl, titel, interpret) in enumerate(charts, 1):
        print(
            f'Platz {platz}: {titel}\t von: {interpret}'
            f'\t\tStimmen: {stimmenanzahl:-{MENU_WIDTH}}'
        )
        

def main():
    print('-' * MENU_WIDTH)
    print('MENUE'.center(MENU_WIDTH))
    print('-' * MENU_WIDTH)
    print('(A)nzeige der Chartliste')
    print('(C)hartliste erweitern')
    print('(L)öschen von Einträgen in der Chartliste')
    print('(M)odifizieren von Einträgen in der Chartliste')
    print('(E)nde')
    print('-' * MENU_WIDTH)
    print()
    while True:
        charts = charts_laden()
        auswahl = input('Treffe deine Auswahl: ').lower()
        if auswahl == 'e':
            break
        elif auswahl == 'a':
            charts_anzeigen(charts)
        elif auswahl == 'c':
            charteintraege_eingeben(charts)
        elif auswahl == 'l':
            charteintraege_loeschen(charts)
        elif auswahl == 'm':
            charts_modifizieren(charts)
        else:
            print('Unbekannte Eingabe!')


if __name__ == '__main__':
    main()
Pickle ist IMHO kein wirklich gutes/sinnvolles Format für Daten die längerfristig gespeichert werden. Insbesondere hier, wo nur Grunddatentypen gespeichert werden, würden sich JSON oder gar CSV als einfacherere Formate die nicht auf Python beschränkt sind, eher anbieten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ _blackjack_ :
wow, danke für deine kostbare Zeit und die Mühe der ausführlichen Erklärungen !
Ich werde jetzt erst einmal alles in Ruhe durcharbeiten, die entsprechenden Dokus lesen, um das ganze zu verinnerlichen, um dann das Skript zu erweitern (Stimmenabgabe, Voting).
Nochmals vielen lieben Dank, du bist echt klasse !
Ich habe sehr, sehr viel erfahren und werde noch mehr dadurch lernen !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ _blackjack_ :
bin noch dran, macht riesig Spaß !
mal was probieren:
... schade, spoilern geht hier nicht ([spoiler][/spoiler]). Hatte nämlich etwas von deinem Code im Vorbeiscrollen gesehen ... und wollte erst einmal nach deinen Vorschlägen die Sache hinbekommen, denn vom Abschreiben lernt man zwar viel, aber nicht so viel, als wenn man die Fehler selbst machen darf, um Erfahrung(en) zu sammeln.
Ich hatte die Stelle mit *enumerate()* gesehen :oops: ... und f-strings sind der Hammer !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Das geht mir ähnlich. Ich arbeite auch gerne mit F-Strings, da ich die sehr "anschaulich" finde. Zumindest besser als die riesen .format() Strings
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

so, ich habe jetzt das Sript soweit fertig, und meinerseits würde ich sagen: *es macht, was es soll* ... Das Voten möchte ich mit einem anderen Skript realisieren, also bitte diesbezüglich vorab mal keine Vorschläge, mit der Bitte um euer Verständnis.
@ _blackjack_ :
ich habe deine ausführlichen Ratschläge verstanden, und versucht, diese umzusetzen, OHNE dein Skript zu lesen. Bitte nicht böse sein, ich werde es noch lesen. Möchte halt Fehler machen, um schneller/besser lernen und verstehen zu können. Wollte nicht abschreiben. Die Stelle mit der enumerate()-Funktion hatte ich allerdings, wie bereits erwähnt, beim Scrollen gesehen (was mich eigentlich etwas ärgert ...)
ach, ja: ich benutze die Console und bin begeistert !

@ alle:
EIN SPOILER MUSS HER, so werden auch die Einträge kürzer.
Ach, ja, wer die .dmp-Datei haben möchte kann sich gerne bei mir melden. Sind bisher 13 wahnsinnig gute Songs drauf. Ich nenne sie *Songs deluxe* ...
so, hier mein Skript (bitte seid *gnadenlos ehrlich*, wie bisher auch - ich bin hart im Nehmen !)

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Verwalten einer Chartliste
     => Unterverzeichnis 'data' anlegen
     => Unterverzeichnis 'tmp' anlegen """

__author__ = 'Perlchamp'
__date__ = '21.02.2019'

import pickle


CHARTS_FILENAME = 'data/charts.dmp'
BACKUP_FILENAME = 'tmp/charts.dmp'
MENUE_WIDTH = 30                                            #15: zu viele Konstanten ?
SPALTE_TITEL = 33
SPALTE_INTERPRET = 30
CHART_ANZEIGE = 90
MENUE_ANZEIGE = 60
CHART_VORSCHUB_01 = (' ' *3)
CHART_VORSCHUB_10 = (' ' *2)
CHART_VORSCHUB_100 = (' ' *1)
MENUE_VORSCHUB = (' ' *6)


def chartliste_laden() :
    try :
        with open(CHARTS_FILENAME, 'rb') as file :
            charts = pickle.load(file)
##        print(charts)                                     #30: Debugging
    except (FileNotFoundError, EOFError) :                  #31: charts.dmp ist leer oder nicht vorhanden
        print('leere Liste !')
        charts = list()                   
##    except EOFError as error :                            #34: keine Erklärung darüber im Netz gefunden !
##        print("Entschuldigung\nDer Chart-Manager musste beendet werden.\nGrund :  ")
 
    return charts


def charteintraege_loeschen(charts, auswahl) :
    while True :
        print('*** Einträge löschen ***')
##        print(charts)                                     #43: Debugging   
        chartliste_anzeigen(charts, auswahl)
        delete = input('Gebe die Platznummer des zu löschenden Eintrags ein :  ')  #45: eval() oder int() verursachen ValueError (int ... base 10)
        if not delete :                                     #46 zu #45: erlaubte Eingaben: natürliche Zahlen >= 1 bis *len(charts)*
            break                                           #47: als Einzeiler schreiben ?
        try :
            delete = int(delete) -1                            #49: siehe #45
            if (delete < 0) or (delete >= len(charts)) :    #50: erlaubte Eingaben: natürliche Zahlen >= 1 bis *len(charts)*
                print('Diese Eingabe ist ungültig !')
                break
            del charts[delete]
            print('Der Eintrag wurde soeben gelöscht !')
        except (IndexError, ValueError, TypeError) :
            print('Diese Eingabe ist ungültig !')
            break                                           #57: bei der Verwendung von 'continue': speichert bei fehlerhaften Eingaben (delete < 1)

        charteintraege_speichern(charts)
    main()


def charteintraege_modifizieren(charts, auswahl) :
    while True :
        print('*** Einträge ändern ***')
##        print(charts)                                     #66: Debugging
        chartliste_anzeigen(charts, auswahl)
        modify = input('Gebe die Platznummer des zu ändernden Eintrags ein :  ')   #68: eval() oder int() verursachen Error
        if not modify :                                     #69 Nachtrag zu #68: ValueError: invalid literal for int() with base 10: ''
            break
        try :
            modify = int(modify) -1                         #72: siehe #68
            if (modify < 0) or (modify >= len(charts)) :    #73 Nachtrag zu #72: Zuweisung wegen Indizierung [Index -1]
                print('Diese Eingabe ist ungültig !')
                break
        except (IndexError, ValueError, TypeError) :
            print('Diese Eingabe ist ungültig !')
            break                                           #78: bei Verwendung von 'continue': speichert bei fehlerhaften Eingaben (modify < 1)
        stimme_neu = charts[modify][0]                      ## TO DO: ohne Indexing lösen ?!
        titel_neu = input('trage den zu ändernden Titel ein, ansonsten [Enter] :  ')
        if not titel_neu :                                  #81: als Einzeiler schreiben ?
            titel_neu = charts[modify][1]                   ## TO DO: ohne Indexing lösen ?!
        print('neuer Titel:', titel_neu)
        interpret_neu = input('trage den zu ändernden Interpreten ein, ansonsten [Enter] :  ')
        if not interpret_neu :                              #85: als Einzeiler schreiben ?
            interpret_neu = charts[modify][2]               ## TO DO: ohne Indexing lösen ?!
        print('neuer Interpret:', titel_neu)
        charts[modify] = (stimme_neu, titel_neu, interpret_neu) #88: Klammern sind unnötig, dienen der Lesbarkeit
        charteintraege_speichern(charts)
    main()                                                  #90: nötig, da sonst Programm beendet wird
    

def chartliste_erweitern(charts) :
    print('*** Einträge hinzufügen ***')
    print('Abbrechen mit der [Enter]-Taste ohne Eingabe !')
    while True :
        titel = input('Titel : ')
        if not titel :
            break
        interpret = input('Interpret : ')
        if not interpret :
            break
        charts.append((0, titel, interpret))
        charteintraege_speichern(charts)
        print('\nVielen Dank. Die aktuellen Einträge sind in der Liste gespeichert !')
    print('Abbruch: in die Liste wurde nichts eingetragen !')
    main()
        

def charteintraege_speichern(charts) :
    try :
        with open(CHARTS_FILENAME, 'wb') as file :
            pickle.dump(charts, file)
        print('Die Daten wurden soeben gespeichert !')
    except FileNotFoundError :
        print('Die Liste konnte nicht gespeichert werden !')
        
    finally :
        with open(BACKUP_FILENAME, 'wb') as backup :
            pickle.dump(charts, backup)
        print('Es wurde erfolgreich eine BackUp-Datei im Ordner \'tmp\' angelegt !') #121 print()-Anweisung überhaupt ausgeben ?


def chartliste_anzeigen(charts, auswahl) :
    print('-' * (CHART_ANZEIGE))
    print('chartliste'.upper().center(CHART_ANZEIGE))
    print('-' * (CHART_ANZEIGE))
##    print('Liste \'Charts\':', charts)                      #128 Debugging
    charts.sort(reverse=True)                               #Liste absteigend nach Anzahl der Votings sortieren
    for platz, (voting, titel, interpret) in enumerate(charts) :
        if platz < 9 :
            print(f'{CHART_VORSCHUB_01}{platz+1}: {titel:<{SPALTE_TITEL}} von: \
{interpret:<{SPALTE_INTERPRET}} Stimmen: {voting}'
                  )
        elif platz < 99 :
            print(f'{CHART_VORSCHUB_10}{platz+1}: {titel:<{SPALTE_TITEL}} von: \
{interpret:<{SPALTE_INTERPRET}} Stimmen: {voting}'
                  )
        else :
            print(f'{CHART_VORSCHUB_100}{platz+1}: {titel:<{SPALTE_TITEL}} von: \
{interpret:<{SPALTE_INTERPRET}} Stimmen: {voting}'
                  )
    print('-' * (CHART_ANZEIGE) + '\n\n')
    if auswahl not in 'lm' :                                #144: beim Löschen (l) und Modifizieren (m) bitte keine Menu-Auswahl anzeigen
        main()                   


def main() :
    print('-' * (MENUE_ANZEIGE))
    print('MENUE'.center(MENUE_ANZEIGE))
    print('-' * (MENUE_ANZEIGE))
    print(f'{MENUE_VORSCHUB}(A)nzeige der kompletten Chartliste')
    print(f'{MENUE_VORSCHUB}(H)inzufügen neuer Einträge')
    print(f'{MENUE_VORSCHUB}(L)öschen von Einträgen in der Chartliste')
    print(f'{MENUE_VORSCHUB}(M)odifizieren von Einträgen in der Chartliste')
    print(f'{MENUE_VORSCHUB}(E)nde des Chart-Managers')
    print('-' * (MENUE_ANZEIGE) + '\n')
    while True :
        charts = chartliste_laden()                         #159: ! charts nicht mehr eine leere Liste !
        auswahl = input('Treffen Sie eine Auswahl :  ').lower()
        if auswahl == 'e' :
            print('Danke, dass Sie den Chart-Manager benutzt haben.')
            break
        elif auswahl == 'a' :
            chartliste_anzeigen(charts, auswahl)
        elif auswahl == 'h' :
            chartliste_erweitern(charts)
        elif auswahl == 'l' :
            charteintraege_loeschen(charts, auswahl)
        elif auswahl == 'm' :
            charteintraege_modifizieren(charts, auswahl)
        else :
            print('falsche Auswahl !')

        return auswahl
        
    
if __name__ == '__main__' :
    main()
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Pickel ist das falsche Datenformat. Vor dem Doppelpunkt gehört kein Leerzeichen.
Zeile 20ff: die Klammern sind überflüssig.
Zeile 50: die Klammern um die Bedingungen sind überflüssig.
Zeile 60/90/107/145: Funktionen sind keine Sprungmarken. Einfach die Funktion zurückkehren lassen.
`charteintraege_loeschen` und `charteintraege_modifizieren` sind fast identisch. Gemeinsamen Code in Funktionen auslagern.
Zeile 115: wo soll hier ein FileNotFoundError auftreten?
Zeile 121: es gibt 4 verschiedene Anführungszeichen für Strings, so dass das Escapen von ' oder " eigentlich nie nötig werden sollte.
Zeile 132ff: Dafür gibt es Formatierungsangaben, statt dreimal das selbe print zu kopieren. An anderer Stelle benutzt Du das ja schon.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3:
danke für deine schnelle Antwort ...
1. Pickel ist das falsche Datenformat. Vor dem Doppelpunkt gehört kein Leerzeichen.
2. Zeile 20ff: die Klammern sind überflüssig.
3. Zeile 50: die Klammern um die Bedingungen sind überflüssig.
4. Zeile 60/90/107/145: Funktionen sind keine Sprungmarken. Einfach die Funktion zurückkehren lassen.
5. `charteintraege_loeschen` und `charteintraege_modifizieren` sind fast identisch. Gemeinsamen Code in Funktionen auslagern.
6. Zeile 115: wo soll hier ein FileNotFoundError auftreten?
7. Zeile 121: es gibt 4 verschiedene Anführungszeichen für Strings, so dass das Escapen von ' oder " eigentlich nie nötig werden sollte.
8. Zeile 132ff: Dafür gibt es Formatierungsangaben, statt dreimal das selbe print zu kopieren. An anderer Stelle benutzt Du das ja schon.
1. ich weiß. _blackjack_ hat csv oder json vorgeschlagen. Mache ich mir später einen Kopf drüber ... möchte vordergründig einen guten Programmierstil erlernen.
2. sind weg
3. dachte wegen der Lesbarkeit, sind jetzt aber weg
4. da muß ich erst schauen/lesen, wie das geht. Bitte nichts verraten, danke.
5. ja, hatte ich mir auch bereits gedacht. Muß ich auch erst mal eine Idee haben, und auch hier bitte nichts sagen ...
6. na, es wurde kein Unterverzeichnis *data* angelegt, oder werden Verzeichnisse bei der Methode "w", "wb" angelegt? - weiß ich jetzt gar nicht (mehr) ...
7. erledigt
8. erledigt
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3 :
4. return main() ?

und in def main() auch bei allen Aufrufen der Funktionen ein *return* setzen? Nein, oder ?

EDIT:
bei allen *Sprungmarken* ein 'return' davor ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sirius3 :
[...] Funktionen sind keine Sprungmarken. Einfach die Funktion zurückkehren lassen.
ok, nach ausgiebigem Testen habe ich festgestellt, dass man jeweils nur vor main() ein "return" setzen *darf* EDIT: /sollte/kann ...
ich verstehe es trotzdem nicht so ganz, denn ich habe ja noch immer Funktionen als *Sprungmarken* in den anderen Funktionen, auch in main(), die sich somit gegenseitig die 'Klinke in die Hand geben' ...
heißt dies, dass nur Verweise zum Hauptprogramm *returned* werden sollen, und wenn ja, warum nicht ?
Ist dies nicht etwas 'unkonsequent' ?

Legende:
=> ^= funktionsaufruf(blablub [, bla_blub]) als *Sprungmarke*
<=> ^= return Funktionsaufruf(blablub [, bla_blub])
- - - ^= kein return, kein funktionsaufruf(blablub [, bla_blub])

Beispiel :
main() => charteintraege_loeschen(charts, auswahl) => chartliste_anzeigen(charts, auswahl) - - - charteintraege_loeschen(charts, auswahl) => charteintraege_speichern(charts) - - - charteintraege_loeschen(charts, auswahl) <==> main()
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Dein Beitrag ist ein bisschen kryptisch.

Funktionen bzw. Methoden werden aufgerufen, ggf. werden ihnen Parameter übergeben und sie kehren immer mit einem Rückgabewert zu der Stelle im Code zurück, von wo sie aufgerufen wurden.
Ist der Rückgabewert nicht mit return explizit angefordert, ist der Rückgabewert None. Das ist also falsch.

Wenn du also in einer Funktion main() aufrufst, dann steckst du noch immer in der Funktion und zusätzlich in main. Wenn du das oft genug machst, läufst du ins Rekursionslimit.

Mit "return main()" würde es identisch passieren, nur dass deine Funktion das Ergebnis von main zurückliefern möchte und darauf wartet.

Vielleicht hilft das hier beim Verständnis: https://py-tutorial-de.readthedocs.io/d ... definieren
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sparrow:
ja, irgendwie haben wir kleine Verständigungsprobleme in der Darstellungs- oder Wortformulierungsweise ...
du kannst ja den Code ansehen und auch kommentieren, aber das ist hier (für dich) ja auch keine Pflichtveranstaltung.

^= bedeutet *entspricht*
*funktionsuafruf(blablub [, blablub])* bedeutet, dass eine Funktion/Methode in einer anderen Funktion/Methode inklusive einem oder (optional) einem weiteren Parameter/Argument aufgeführt wird, wobei dies:
==> = OHNE return davor
<==> = mit return davor
- - - = nicht in Funktion/Methode aufgeführt, d.h. diese Funktion/Methode läuft OHNE Aufruf einer anderen Funktion/Methode durch

das *Beispiel* 'beschreibt' den Verlauf, welchen das Skript nimmt, wenn ich es starte und die Taste "l" (für *Eintrag löschen*) drücke, wobei main() ja die erste bzw. Hauptfunktion ist, die aufgerufen bzw. gestartet wird. Da die von Sirius3 genannten *Sprungmarken* alle am Ende der jeweiligen Funktionen/Methoden stehen, müssen diese ja eigentlich auf nichts mehr warten, da fertig durchgelaufen ...
ich werde mir deinen Link natürlich noch ansehen, danke dafür !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ sparrow:
ja wir haben wirklich Verständigungsprobleme !

ich habe mir deinen Link angeschaut, der nicht im geringsten mit meinem *Thema* zu tun hat ...
nochmals:

Code: Alles auswählen

def blablub(param1, param2):
    blablub
    irgendwas(param1, param2)  # Funktion ist/als 'Sprungmarke' ?, return davor setzen ?
    weiterer_code
    main()  # Funktion ist/als 'Sprungmarke' ?, return davor setzen ? steht am Ende des Codes der Funktion

def irdendwas(param1, param2):
    code
    nochetwas(param1)  # Funktion ist/als 'Sprungmarke' ?, return davor setzen ?
    code

def nochwas(param1):
    blablub(param1, param2)  # Funktion ist/als 'Sprungmarke' ?, return davor setzen ?
    code
    main()  # Funktion ist/als 'Sprungmarke' ?, return davor setzen ? steht am Ende des Codes der Funktion

def main():
    if trulla = "m":
        blablub(param1, param2)  # Funktion ist/als 'Sprungmarke' ?
    elif trulla = "l":
        irgendwas(param1, param2)  # Funktion ist/als 'Sprungmarke' ?
    else:
        code
    weiterer_code
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Der Link löst genau dein Problem, denn dir fehlt offensichtlich das Verständnis, was Funktionen sind und was return tut. Da helfen auch viele blablubs nicht.
Und genau das wird in dem verlinkten Tutorial ausführlich erklärt.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

ich will mich nicht streiten ...
in dem Artikel steht, wie Funktionen aufgebaut sind (Kopf, Körper), wie man sie benutzt. Des Weiteren wird erklärt, wofür Parameter/Argumente da sind und warum, und dass man diese (aber auch durch die jeweilige Funktion/Methode ermittelte Objekte/Werte/blablub) mittels return an andere Funktionen/Methoden weitergeben kann, damit sie im lokalen Namensraum dieser (zweiten/anderen) Funktion(en)/Methode(n) benutzt und wieder verwendet/verarbeitet werden können. Kein return bedeutet die Rückgabe von None, was bedeutet, dass es sich im Endeffekt um eine Prozedur und nicht um eine Funktion/Methode handelt.

ich rufe aber innerhalb einer Funktion/Methode andere Funktionen/Methoden (daher 'Sprungmarke') auf. Wie man das händelt steht in diesem Artikel nicht. Und natürlich fehlt mir als Anfänger z.T. das Verständnis bzw. habe ich als Anfänger nicht den Experten-Durchblick, WESHALB ich auf *konkrete* Beispiele angewiesen bin.
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Nein, du bist nicht auf konkrete Beispiele angewiesen, sondern darauf, Dokumentation zu lesen und zu verstehen. Programmieren funktioniert nicht durch raten. Mal ganz angesehen davon, dass es ja Beispiele in dem Tutorial gibt.
Grundsätzliches Basiswissen, das jedes Tutorial vermittelt, sind Funktionen, Datenstrukturen, Exception-Handling. Das ist das Minimum, das man braucht um in Python effektiv etwas tun zu können.

Und return (das ist englisch für "zurückkehren") gibt nichts weiter, sondern zurück. Was auch in dem Link beschrieben ist.
Eine Funktion ist eben keine Sprungmarke, wie dir hier bereits gesagt wurde. Deshalb wird das da auch nicht behandelt. Man ruft eine Methode (ggf. mit Paramtern) und bekommt von dort ein Ergebnis zurück. Das sind Funktionen. Deshalb macht dein Aufruf von main() am Ende der Funktion keinen Sinn, weil der Programmfluss eh an die Stelle zurückkehrt. Im Gegenteil. Dort main() aufzurufen ist schlicht falsch, weil es eine unnötige Rekursion herbeiführt.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

@Perlchamp: Das Wort Sprungmarke habe ich zuletzt Anfang der 80er Jahre beim Erlernen von BASIC gelesen/benutzt.
Vergiss dieses MARKE: goto MARKE in Verbindung mit Python.
Wenn du dich davon nicht trennen kannst, kommst du gedanklich nicht aus der Sackgasse raus, in der du gerade bist.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThomasL: Zur Ergänzung: das ganze *nur* mit GOTOs zu Sprungmarken zu lösen wäre ja noch okay, aber Python hat quasi nur GOSUBs und das wäre auch in BASIC ein Problem/Fehler immer mit GOSUBs Sprungmarken anzuspringen aber nie ein RETURN zu verwenden.

@Perlchamp: Bei Deinem letzten (Pseudo-)Beispielcode musst Du nicht irgendwo ``return`` davor setzen sondern ganz einfach die `main()`-Aufrufe *sein lassen*. Du musst nicht etwas machen, was deswegen auch nicht im Tutorial gezeigt wird, sondern Du musst etwas *nicht machen*.

Mit dem `main()`-Aufruf springt man nicht einfach an den Anfang des Code in der `main()`-Funktion, sondern ruft die noch einmal von neuem auf, während sie bereits mindestens einmal läuft und dieser frühere Aufruf/diese früheren Aufrufe noch gar nicht zu ende abgearbeitet wurde(n). Was auch bedeutet, dass die Funktionen, welche von dort aufgerufen wurden auch noch gar nicht komplett abgearbeitet wurden. Du sammelst damit immer mehr aktive Funktionsaufrufe an. Die belegen Speicher für die lokalen Variablen und die Verwaltung des Programmablaufs. Speziell letzterer Speicher ist nicht nur/unbedingt in der Grösse beschränkt, sondern insbesondere auch durch das Rekursionslimit, also die Anzahl wie oft solcher Speicher angefordert aber nicht wieder freigegeben werden kann. Wenn das Limit erreicht wird, bricht Python einfach mit einer `RecursionError`-Ausnahme ab. Rekursion ist kein Ersatz für einfache Schleifen!
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@ _blackjack_ :
ok, das verstehe ich ja. Wie bekomme ich denn aber dann eine Benutzerführung hin, also ein Menu für Benutzereingaben, das immer dann eingeblendet wird, wenn eine vorherige Aufgabe abgeschlossen ist ? Wenn ich nun folgenden Code habe :

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Verwalten einer Chartliste
     => Unterverzeichnis 'data' anlegen
     => Unterverzeichnis 'tmp' anlegen """

__author__ = 'Perlchamp'
__date__ = '21.02.2019'

import pickle


CHARTS_FILENAME = 'data/charts.dmp'
BACKUP_FILENAME = 'tmp/charts.dmp'
MENUE_WIDTH = 30 
SPALTE_TITEL = 35
SPALTE_INTERPRET = 36
SPALTE_STIMMEN = 11
CHART_ANZEIGE = 103
MENUE_ANZEIGE = 60
CHART_VORSCHUB_01 = ' ' *3
CHART_VORSCHUB_10 = ' ' *2
CHART_VORSCHUB_100 = ' ' *1
MENUE_VORSCHUB = ' ' *6


def chartliste_laden() :
    try:
        with open(CHARTS_FILENAME, 'rb') as file:
            charts = pickle.load(file)
    except (FileNotFoundError, EOFError):
        print('leere Liste !')
        charts = list()                   
 
    return charts


def chartliste_anzeigen(charts, auswahl):
    print('-' * (CHART_ANZEIGE))
    print('chartliste'.upper().center(CHART_ANZEIGE))
    print('-' * (CHART_ANZEIGE))
    charts.sort(reverse=True)
    for platz, (voting, titel, interpret) in enumerate(charts):
        zeile_tabelle = f'{platz+1}: {titel:<{SPALTE_TITEL}} von: {interpret:<{SPALTE_INTERPRET}}{voting:>{SPALTE_STIMMEN}} Stimmen'
        if platz < 9:
            print(f'{CHART_VORSCHUB_01}' + zeile_tabelle)
        elif platz < 99:
            print(f'{CHART_VORSCHUB_10}' + zeile_tabelle)
        else:
            print(f'{CHART_VORSCHUB_100}' + zeile_tabelle)
    print('-' * (CHART_ANZEIGE) + '\n\n')


def main():
    print('-' * (MENUE_ANZEIGE))
    print('MENUE'.center(MENUE_ANZEIGE))
    print('-' * (MENUE_ANZEIGE))
    print(f'{MENUE_VORSCHUB}(A)nzeige der kompletten Chartliste')
    print(f'{MENUE_VORSCHUB}(E)nde des Chart-Managers')
    print('-' * (MENUE_ANZEIGE) + '\n')
    while True:
        charts = chartliste_laden()
        auswahl = input('Treffen Sie eine Auswahl :  ').lower()
        if auswahl == 'e':
            print('Danke, dass Sie den Chart-Manager benutzt haben.')
            break
        elif auswahl == 'a':
            chartliste_anzeigen(charts, auswahl)
        else :
            print('falsche Auswahl !')

        return auswahl
        
    
if __name__ == '__main__' :
    main()

, dann ist das Programm nach der Anzeige der Chartliste beendet, will ich aber nicht, hätte gerne wieder ein Auswahlmenu angezeigt !

hier der Inhalt von CHARTS_FILLENAME :

Code: Alles auswählen

€]q (K X   you're crazyqX   Guns 'n' Rosesq‡qK X   you know i'll always love youqX   Budgieq‡qK X   you could be mineqX   Guns 'n' Rosesq‡q	K X   where did you sleep last nightq
X   Nirvanaq‡qK X   what should be doneq
X
   Uriah Heepq‡qK X   welcome to the jungleqX   Guns 'n' Rosesq‡qK X   the real thingqX
   Faith No Moreq‡qK X   suicide is painlessqX   Manic Street Preachersq‡qK X   straight up and downqX   The Brian Jonestown Massacreq‡qK X   stairway to heavenqX   Led Zeppelinq‡qK X   schoolqX
   Supertrampq ‡q!K X
   paradise cityq"X   Guns 'n' Rosesq#‡q$K X   paradiseq%X	   Jeff Bealq&‡q'K X
   november rainq(X   Guns 'n' Rosesq)‡q*K X   nobody's fault but mineq+X#   Jimmy Page (feat. The Black Crowes)q,‡q-K X
   no excusesq.X   Alice In Chainsq/‡q0K X
   nach Hauseq1X   Seligq2‡q3K X
   lucy bluesq4X
   Uriah Heepq5‡q6K X   lass mich reinq7X   Seligq8‡q9K X   kashmirq:X   Led Zeppelinq;‡q<K X   i need loveq=X   Deep Purpleq>‡q?K X   home sweet homeq@X
   Mötley CrüeqA‡qBK X   hocus pocusqCX   FocusqD‡qEK X   hells bellsqFX   AC/DCqG‡qHK X   ghostqIX   Slash (feat. Ian Astbury)qJ‡qKK X   fly awayqLX
   Lenny KravitzqM‡qNK X   fight the good fightqOX   TriumphqP‡qQK X
   enter sandmanqRX	   MetallicaqS‡qTK X	   civil warqUX   Guns 'n' RosesqV‡qWK X
   can't you seeqXX   The Marshall Tucker BandqY‡qZK X   breadfanq[X   Budgieq\‡q]K X   beautiful dangerousq^X   Slash (feat. Fergie)q_‡q`K X
   back in blackqaX   AC/DCqb‡qcK X   back from caliqdX   Slash (feat. Myles Kennedy)qe‡qfK X   back and forth againqgX   Slashqh‡qiK X   at lastqjX
   Etta Jamesqk‡qlK X	   assassingqmX	   Marillionqn‡qoK X   amazingqpX	   Aerosmithqq‡qrK X
   after darkqsX   Tito & Tarantulaqt‡quK X   Tage wie dieseqvX   Die Toten Hosenqw‡qxK X   SuperboyqyX   Nina Hagen Bandqz‡q{K X   Schrei nach Liebeq|X
   Die Ärzteq}‡q~K X   Arsch einer GöttinqX   Seligq€‡qe.
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Antworten