pickle , Probleme mit dem Wiederherstellen

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.
Antworten
zikzak
User
Beiträge: 21
Registriert: Sonntag 22. September 2013, 07:28
Wohnort: Sipbachzell, Österreich

Hallo geschätzte Python-Freunde,
ich bin neu hier und auch Anfänger mit Python3.

Folgendes Problem:

1. dump als Klassenfunktion:

Code: Alles auswählen

def save(self, file):
        directory = setDirectory()
        with open(directory + "/" + file, "wb") as fp:
            try:
                pickle.dump(self, fp,pickle.HIGHEST_PROTOCOL)
            except csv.Error as e:
                sys.exit('file {}, {}'.format(file, e))
2. dump als externe Funktion:

Code: Alles auswählen

def saveag(agklasse, file):
        directory = setDirectory()
        with open(directory + "/" + file, "wb") as fp:
            try:
                pickle.dump(agklasse, fp,pickle.HIGHEST_PROTOCOL)
            except csv.Error as e:
                sys.exit('file {}, {}'.format(file, e))   
Beide kann ich mittels einer externen Funktion laden und sie sind ident:

Code: Alles auswählen

def loadag():
        file = setFile()
        with open(file, "rb") as fp:
            try:
                return pickle.load(fp)
            except IOError:
                print ("Fehler beim Laden der SAPAG.dat")
            else: fp.close()
Warum aber funktioniert die Klassenfunktion nicht ?

Code: Alles auswählen

def load(self):
        file = setFile()
        with open(file, "rb") as fp:
            try:
                self = pickle.load(fp)
            except IOError:
                print ("Fehler beim Laden der SAPAG.dat")
            else: fp.close()
Zuletzt geändert von Anonymous am Sonntag 22. September 2013, 10:06, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Sirius3
User
Beiträge: 17760
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo zikzak,
ich weiß zwar nicht, was die »setDirectory«-Funktion macht, aber sicherlich nicht das, was ihr Name verspricht. Da sie keinen Paremeter übergeben bekommt, kann sie auch nichts setzen. Die Funktion hieße wohl besser »get_directory«. Um Pfade zusammen zu setzen gibt es in Python »os.path.join«. Das sorgt auch dafür, dass unter Systemen, wo '/' nicht der Verzeichnistrenner ist, alles richtig funktioniert. Ich weißt auch nicht was »csv.Error« für eine Exception ist, dass sie aber bei einem dump auftritt, sollte auch nicht vorkommen. Das Programm dann mit »sys.exit« zu beenden fällt Dir spätestens dann auf die Füße, wenn Dein Programm komplexer wird, Du irgendwann die Klasse wiederverwenden willst oder jemand anderes Deinen Code benutzen will. Exceptions sind dazu da, dass sie solange weiter gereicht werden, bis jemand etwas Sinnvolles damit anfangen kann, falls das niemand kann, wird das Programm mit einer Fehlermeldung beendet. Das Programm zu beenden ist also nichts Sinnvolles, weil es nur die allerletzte Notlösung ist.

Deine »load«-Klassenfunktion tut genau das, was Du geschrieben hast, ob sie „funktioniert“ darüber läßt sich streiten. »self« ist eine lokale Variable, wie jede andere auch. Wenn Du also »self« mit einem anderen Wert belegst, ist das dem Objekt, das davor über »self« referenziert wurde, ziemlich egal. Mit »pickle.load« lädt man auch ein Objekt und nicht den Inhalt eines Objekts.

Für »setFile« gilt das gleiche wie für »setDirectory«. Die Fehlermeldung bezieht sich auf eine "SAPAG.dat", die aber gar nicht die Datei sein muß, die geladen wird. Was passiert nachdem Du mit »print« eine Fehlermeldung ausgegeben hast? Ist das Programm danach noch in einem Zustand, mit dem sich sinnvoll weiterarbeiten läßt? Es fehlt dann nämlich ein »return«.
Ein »close« ist bei »with« unnötig.
BlackJack

@zikzak: Was bedeutet denn „funktioniert […] nicht”? Erstellst Du ein Objekt, rufst darauf dann `load() auf und erwartest das die Zuweisung an den *lokalen* Namen `self` irgendwie auf magische Weise das Objekt durch etwas anderes ersetzt? So funktionieren Zuweisungen in Python nicht. Du bindest einfach nur den lokalen Namen an einen anderen Wert als das Argument was der Methode übergeben wurde. So eine Zuweisung wirkt sich immer nur auf den Namen aus, nie auf ein Objekt was *vorher* mal an diesen Namen gebunden war.

Du brauchst eine statische Methode (`staticmethod()`) die man auf der Klasse aufrufen kann ohne das man dafür vorher ein Exemplar davon erstellen muss. Und das was Du „Klassenfunktion” nennst ist eine Methode. Manchmal sagt man auch Instanzmethode dazu. Es ist auch keine Klassenmethode (`classmethod()`).

Noch ein paar Anmerkungen zu den Quelltextschnippseln: Die Namen halten sich teilweise nicht an den Style Guide for Python Code und teilweise sind sie falsch.

`file` bei den `save*()`-Varianten ist keine Datei sondern ein Datei*name*. Zudem ist `file` der Name eines eingebauten Datentyps. Das könnte also zu Verwirrung beim Leser führen wenn man den für etwas anderes verwendet.

`setDirectory()` ist falsch weil da augenscheinlich gar nichts gesetzt wird. Dazu bräuchte es ein Argument auf *was* das Verzeichnis gesetzt wird. Das sieht eher nach einer `get*`-Funktion aus. Das gleiche gilt später für `getFile()`. An der Stelle kann man übrigens auch ein Entwurfsproblem vermuten, solange die beiden Funktionen nicht irgendwelche ”festen” Werte aus einer Konfigurationsdatei oder so ähnlich liefern. Aber selbst dann sollte man das aus Gründen der Flexibiltät ausserhalb der Funktion oder Methode ermitteln und als Argument übergeben.

Pfade setzt man mit nicht mit ``+`` und ``'/'`` sondern mit `os.path.join()` zusammen.

`pickle.dump()` wird unter normalen Umständen niemals einen `csv.Error` auslösen, das macht also absolut keinen Sinn diese Ausnahme dort zu behandeln. Zudem hat `sys.exit()` dort nichts zu suchen. Solange der Code nicht ganz eng zum Hauptprogramm gehört sollte er nicht einfach das gesamte Programm abbrechen dürfen. Das nimmt dem Code der die Speichern-Methode oder -Funktion aufruft jede Möglichkeit anders auf einen Fehler zu reagieren.

Abkürzungen die nicht allgemein bekannt ist, machen Namen unverständlich. Was ist `ag`? Kann man dafür nicht einen für den Leser verständlicheren Namen finden?

`agklasse` bei `saveag()` ist falsch weil dort ganz bestimmt keine Klasse übergeben wird sondern ein Objekt das aus der Klasse erstellt wurde. Da Klassen in Python auch Objekte sind die man wie andere auch als Argumente übergeben kann, ist der Name an der Stelle irreführend.

Wenn man ``with`` für die Dateien verwendet, dann braucht man kein `close()` mehr aufrufen. Das ist ja gerade der Sinn von ``with``, dass wenn der Kontrollfluss den ``with``-Block verlässt, aus welchem Grund auch immer, die Methode zum „aufräumen” der Ressource aufgerufen wird.

Die Fehlerbehandlung ist auch beim Speichern unpassend. Den Benutzer informieren dass etwas nicht geladen werden konnte, und dann einfach so weitermachen als wäre nichts passiert, ist keine gute Idee. Wenn der Aufrufer wissen möchte, dass das Laden fehlgeschlagen ist, hat er nicht die Ausnahme sondern muss den Rückgabewert prüfen. Dieses auf spezielle Rückgabewerte prüfen soll ja gerade durch Ausnahmen ersetzt werden. Dafür wurden die mal erfunden. Der Rückgabewert wird im Fehlerfall noch nicht einmal explizit gemacht. Da fragt man sich als Leser ob das Absicht war oder ob der Programmierer an diesen Fall überhaupt nicht gedacht hat.

Edit: Noch ein Nachtrag zu `pickle`: Für längerfristige Persistenz sollte man sich sicher sein, dass sich in Zukunft nichts an den Datentypen ändert die man damit speichert, sowohl was die Klassen selber angeht, als auch in welchem Packages/Modulen die sich befinden. Denn wenn sich inkompatible Änderungen ergeben, kann man die alten Daten natürlich nicht mehr laden. Relativ sicher vor so etwas ist man wenn man sich auf Python-Grunddatentypen beschränkt. In dem Fall ist man aber schon ziemlich in der Nähe von JSON, wofür es in der Standardbibliothek das `json`-Modul gibt. JSON hat den Vorteil das es für das Format Bibliotheken/Unterstützung auch in sehr vielen anderen Programmiersprachen gibt.
zikzak
User
Beiträge: 21
Registriert: Sonntag 22. September 2013, 07:28
Wohnort: Sipbachzell, Österreich

Zunächst vielen Dank für die schnellen Antworten !
Ich merke schon, da habe ich noch viel zu lernen ...

zunächst zur Klasse AG:

Code: Alles auswählen

class AG(dict):
    def __init__(self, ag=None,we=None): # ag[k] ist ein String, we eine list von Strings
        if ag:
            self.__add__(ag, we)
Ich habe zwecks Übung einfach von dict abgeleitet, da die AG-Nummer eindeutig sind.
Das Laden aus einer csv-Datei funktioniert:

Code: Alles auswählen

def readcsv (self):
        filename = setFile()
        with open(filename,newline='', encoding='utf-8') as rfile:
            try:
                reader = csv.reader(rfile, delimiter=";")
                next(reader)
                """        
                Spaltenbezeichner übersprungen, nun zu den Daten                
                """
                for r in reader:
                    self.__add(r[2], r[3])                                    
            except csv.Error as e:
                sys.exit('file {}, line {}: {}'.format(filename, reader.line_num, e))   
Die Methode zum Laden habe ich mal ausgelagert, dass mit @staticmethod habe ich getestet und hat auch funktioniert.

Code: Alles auswählen

def loadag():
        datei = setFile()
        with open(datei, "rb") as fp:
            try:
                return pickle.load(fp)
            except pickle.PickleError:
                print ("Fehler beim Laden SAPAG.dat")
Danke !!!
Zuletzt geändert von Anonymous am Sonntag 22. September 2013, 18:16, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Antworten