Seite 2 von 3
Re: Generelles Vorgehen beim Programmieren
Verfasst: Mittwoch 14. August 2019, 13:33
von __blackjack__
@schnickalot: Das Klassen bzw. Objekte gepicklet werden können, da braucht man auf nicht allzuviel achten. Das ist der Normalfall würde ich mal sagen. Ausnahmen sind eher Werte die *nicht* gepicklet werden können. Zum Beispiel Dateiobjekte, weil die eine externe Abhängigkeit die man nicht sichern kann (und eigentlich auch nicht will). Nämlich die (offene) Datei auf Betriebssystemebene und deren Zustand, also kompletten Inhalt + Position bei der man gerade ist (falls die Datei ”seekable” ist).
Das Problem ist das man eingeschränkt wird was man an den Klassen noch ändern darf/kann ohne das es zu Problemen kommt wenn man nach Änderungen ältere Pickle-Dateien wieder einlesen will. Wenn man die Klasse umbenennt oder in ein anderes Modul verschiebt kann man keine Exemplare davon mehr entpickeln, weil die Klasse nicht mehr gefunden werden kann. Die Struktur der Klasse darf man dann auch nicht mehr verändern.
Pickle ist gut und nützlich um kurzfristig Python-Objekte zu serialisieren oder wenn es kein Problem ist, wenn das deserialisieren nicht mehr klappt. Zum Beispiel wenn man Objekte zu einem anderen Prozess übertragen möchte oder berechnete Werte extern Cachen möchte, die man zur Not einfach neu berechnen kann.
Für längerfristige Speicherung sollte man explizit Code schreiben der die Daten in einem standardisierten Format (de)serialisiert. Am besten auch mit einer Versionsnummer versehen, so das man bei Änderungen am Format erkennt in welcher Version die gespeicherten Daten vorliegen und man entsprechend auch unterschiedlichen Code für die Versionen haben kann, also auch alte Daten noch einlesen kann, sofern die Änderungen nicht so stark sind, das man das nicht automatisch konvertieren kann, oder zumindest mit ein paar Rückfragen an den Benutzer.
”Pythonisch” ist es möglichst einfach und verständlich zu halten. Properties einzusetzen wo es nur geht gehört da sicher nicht dazu, denn das ist ganz viel Code der nichts macht. Wenn Du triviale Getter und Setter hast, dann bringt das doch effektiv gar keinen Unterschied dazu einfach die Attribute öffentlich zu machen, denn das sind sie effektiv ja. Wenn man für ein Attribut einen Getter und einen Setter hat, dann ist da nichts gekapselt. Wenn man das dann auch noch als Property verfügbar macht, dann ändert sich ja nicht einmal am Code der das benutzt etwas. Das ist einfach nur viel Schreibarbeit für *nichts*.
klein_mit_unterstrichen betrifft übrigens auch Methodennamen.
So wie Du `property` verwendest macht das heute auch niemand mehr. Seit dem es die Dekoratorsyntax gibt, schreibt man das so:
Code: Alles auswählen
@property
def some_value(self):
return self._some_value + 1
@some_value.setter
def some_value(self, value):
self._some_value = value - 1
Properties sind einer der Gründe mit denen die Dekoratorsyntax eingeführt wurde, damit man das gleich am Anfang sieht und nicht erst nach zwei Methoden ein Attribut eingeführt wird. Die Getter und Setter in der Art wie Du sie geschrieben hast, wären bei mir übrigens ein Kandidat für einen führenden Unterstrich gewesen, sonst hätte man zwei offizielle Wege das gleiche zu tun. Das stellt einen bei der Verwendung dann vor die eigentlich unnötige Frage ob man nun `some_objekt.get_some_value()` schreibt oder `some_objekt.some_value`. Wenn man ein Property einführt, will man ja eigentlich letzteres, sonst hätte man sich das sparen können.
Ja, Datenkapselung ist eine gute Idee, aber eben nur da wo es Sinn macht und nochmal: Wenn man triviale Getter/Setter hat, dann ist da nix gekaspelt. Weil es effektiv ja überhaupt gar keinen Unterschied gibt ob so ein Wert nun als Attribut vorliegt, oder ob man den kleinen Umweg über Methoden geht – man kann an den Wert von aussen problemlos heran kommen und man kann ihn problemlos setzen.
Und in Python ist man generell nicht so paranoid alles verbieten/erzwingen zu wollen. Man geht von einem mündigen Programmierer aus, der weiss was er tut, und mit den Konsequenzen seines Handelns klar kommt. Man könnte beispielweise für `score` ein reines `get`-Property schreiben und eine `increase_score()` Methode, damit auch ja niemand etwas anderes machen kann als `score` um 1 zu erhöhen. Oder man lässt `score` öffentlich und bietet vielleicht eine `increase_score()` Methode an – oder auch nicht.
Mein Beispiel ist doch nicht wirklich fortgeschritten. Ich finde das im Gegenteil sehr einfach, und es unterscheidet sich bei dem was da steht auch kaum von der ebenfalls einfachen Klasse die Sirius3 geschrieben hat. Der Vorteil vom `attr`-Modul gegenüber so einfachen selbst geschriebenen Klassen ist, das man neben der Schreibarbeit die man sich in der `__init__()` spart, das man auch eine `__repr__()` und die ganzen Vergleichsfunktionen frei Haus bekommt:
https://www.attrs.org/en/stable/why.htm ... en-classes
Re: Generelles Vorgehen beim Programmieren
Verfasst: Mittwoch 14. August 2019, 15:34
von schnickalot
das Pickle nicht mit Änderungen klar kommt, habe ich auch schon erleben dürfen. Hab aber erstmal drüber hinweggesehen, weil ich ja noch am üben bin und dachte wenn erstmal alles steht, sollten auch keine Änderungen mehr kommen. Da sieht man mal wie falsch man liegt. War ja schon froh überhaupt mal eine Datei geschrieben zu haben.
Vielen Dank für eure Geduld. Ich seh schon an euren Antworten, dass ich noch eine ganze Ecke weg bin von jeglicher Programmiertechnik. Ich bastel wohl noch an einzelnen Schritten rum, wobei ich wohl auch oft die falschen Module/alte Schreibweisen verwende. Schon allein die Konventionen.. war mir so sicher, dass Attribute klein geschrieben werden, Methoden klein und gross weiter und Klassen gross und gross weiter. Aber ok.. wo solls auch herkommen - in Anfängerbüchern steht das alles nicht bzw. ist evtl. veraltet. Ich wünschte ich könnte mal eine Woche bei einem Profi Praktikum machen und dumme und doofe Fragen stellen dürfen. Ich frage mich wo man all diese Sachen lernen kann? Wie kommt man auf gewisse Techniken ohne in einem Forum zu fragen oder zu googlen? Will euch nicht mit jedem einzelnen Thema mit 2stelligen Nachfragen nerven. Habt ihr ein Tipp wie man da effektiv vorankommt?
Re: Generelles Vorgehen beim Programmieren
Verfasst: Mittwoch 14. August 2019, 15:52
von __deets__
Doch, durch Foren. Und dann recherchieren. Wenn hier wer „nach PEP8 werden Methoden klein_mit_untertstrich“ schreibt, kann man eben mal nach PEP8 suchen. Und liest über all die anderen Dinge darin etwas. Auch „neue“ Features wie eben die properties mit .setter kann man dadurch entdecken. Dazu muss man eben dran bleiben, sich die Probleme und Lösungen anderer Leute hier oder anderswo (ich habe hauptsächlich mit der comp.lang.python newsgroup gelernt, aber news ist so 0er Jahre. Wenn überhaupt

) aktiv anschauen & aus denen etwas lernen, das eben nicht unmittelbar jetzt und ganz gleich den eigenen Problemen entspricht. Aber einen Hinweis für die Zukunft gibt.
Und bezüglich konkreter sprach / Library Features ist das „what’s new in python X“ das mit jeder Version kommt sehr wertvoll. Allerdings ist das im Verhältnis zu Techniken, Bibliotheken, Algorithmen und Datenstrukturen eher irrelevant. Auch mit old style settern oder selbst geschriebenen Klassen statt attrs kommt man zum Ziel. Die wichtigen Dinge sind aber prinzipielle Lösungsansätze und Konzepte, und die werden eben hier für alles mögliche diskutiert.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Mittwoch 14. August 2019, 16:26
von schnickalot
ok verstanden. Danke nochmals an alle für die Fülle an Informationen. Ich denke wir werden uns dann des öfteren unterhalten. Ich versuch die Tage erstmal mit den bisherigen Information mein Progrämmchen umzuschreiben. Bin mir sicher da kommen noch die ein oder anderen Nachfragen. Dann kann ich auch mit mehr fertigen Codeteilen meine Anliegen darstellen.
Sehr angenehmes Forum! Sehr geduldige und ausführliche Helfer! Thumbs up! Man fühlt sich hier gleich wohl

Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 12:04
von schnickalot
Hab mir gestern abend mal JSON angeschaut. Ist ja nicht wirklich anders zu benutzen wie Pickle. Versteh ich das richtig, dass man json.dumps() benutzt um Python Objekte zu JSON Strings zu konvertieren und json.dump(), um es auch noch zusätzlich in eine Datei zu schreiben (also 2 Schritte in einem)?
Habs jetzt mal so:
Code: Alles auswählen
import json
import os
class Players:
def __init__(self):
self.players = {}
self.file = "players.json"
self.load()
def addPlayer(self, fullname, player):
self.players[fullname] = player
def load():
if not os.access(self.file, os.R_OK):
return
with open(self.file) as f:
self.players = json.load(f)
def save():
with open(self.file, 'w') as f:
json.dump(self.players, f)
Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 12:39
von __blackjack__
@schnickalot: Da muss man schon etwas mehr machen, denn das funktioniert ja nur wenn man ausschliesslich Werte hat, deren Typ vom JSON-Modul standardmässig in JSON-Werte umwandeln kann und nicht mit beliebigen Datentypen. Das JSON-Modul wüsste nicht was es beim Speichern mit einem `Player`-Objekt machen sollte. Und beim laden werden aus JSON-Daten auch nicht einfach so `Player`-Objekte. Da braucht man in beide Richtungen jeweils mindestens einen Zwischenschritt der Python-Objekte in eine ”JSON-kompatible” Struktur überführt die man dann speichern kann, und die JSON-Datenstruktur die man geladen hat, wieder in Python-Objekte.
Die `__init__()` sollte wesentlich simpler sein. Die sollte nicht versuchen irgendetwas vom Dateisystem zu laden, sondern einfach nur ein ”leeres” Objekt erstellen. Es wäre praktisch wenn man optional Spieler übergeben könnte, die hinzugefügt werden. Und zwar als iterierbares Objekt das Spieler liefert, also beispielsweise eine Liste, und nicht als Wörterbuch. Das ist die interne Speicherung die Redundanz enthält, denn der volle Spielername ist ja bereits Bestandteil jedes einzelnen Spielers.
An der Stelle ist auch auch die Signatur von `addPlayer()` falsch – den vollen Namen sollte man nicht übergeben, denn dann kann man ja so komische Sachen machen wie ``players.add_player('Peter Meier', Player('Anna', 'Schulze'))``. Man will doch sicher nicht Anna Schulze über 'Peter Meier' ansprechen, sondern über 'Anna Schulze'.
`file` ist ein passender Name für eine Datei, aber nicht für einen Datei*namen*. Wenn ich was habe was `file` heisst, dann erwartet der Leser, dass das eine `read()` und/oder `write()`-Methode hat und ist verwirrt wenn das als Argument für `open()` verwendet wird – denn `open()` bekommt keine Datei als Argument sondern hat eine Datei als Rückgabewert.
Ich würde den Dateinamen gar nicht als Attribut von `Players` speichern sondern sowohl bei `load()` als auch bei `save()` als Argument übergeben.
Und `load()` würde ich als Klassenmethode implementieren die nicht ein Objekt füllt, sondern ein neues `Players`-Objekt liefert.
Textdateien sollte man immer mit einer expliziten Kodierung öffnen. Auch wenn JSON eigentlich nur für ASCII spezifiziert ist, würde ich UTF-8 verwenden. Denn von Hand schreiben die wenigsten Leute Escape-Sequenzen für ”Sonderzeichen” in solche Dateien.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 13:40
von Sirius3
@schnickalot: zusätzlich zu dem was __blackjack__ schon geschrieben hat: eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht Tabs. In `load` auf irgendeine Dateieigenschaft zu prüfen und im Fehlerfall stillschweigend nichts zu machen, ist sehr verwirrend. Einfach probieren, ob `open` klappt, und falls nicht, wird es eine Exception geben, die Du an passender Stelle verarbeiten kannst.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 13:58
von __blackjack__
Folgender Code erwartet das `Player` eine Methode `to_dict()` und eine Klassenmethode `from_dict()` hat (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import json
class Players:
def __init__(self, players=()):
self._players = {}
for player in players:
self.add(player)
def add(self, player):
self._players[player.full_name] = player
def save_json(self, filename):
data = {
"players": [player.to_dict() for player in self._players.values()]
}
with open(filename, "w", encoding="utf8") as file:
json.dump(data, file)
@classmethod
def load_json(cls, filename):
with open(filename, encoding="utf8") as file:
data = json.load(file)
return cls(map(Player.from_dict, data["players"]))
Man könnte das laden und speichern aber auch als Funktionen schreiben die generell mit Typen umgehen können die die genannten beiden Methoden haben. Der Funktion zum Laden müsste man dann noch entweder den Datentyp oder besser noch die `from_dict()`-Methode – oder eben generell etwas aufrufbares mitgeben was das gewünschte Objekt liefert.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 15:55
von schnickalot
Danke euch beiden für die Analyse!
@Blackjack: kannst Du mir Dein Beispiel erklären? Mit Klassenmethoden habe ich es noch nicht so. Ich komm nicht dahinter wie das genau funktionieren soll.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Freitag 16. August 2019, 17:50
von __blackjack__
@schnickalot: Methoden bekommen das Objekt auf dem sie aufgerufen wurden als erstes Argument, per Konvention `self` genannt. Und Klassenmethoden bekommen die Klasse als erstes Argument, per Konvention `cls` genannt. Und normalerweise ruft man Klassenmethoden auch auf der Klasse auf. Also hier beispielsweise:
Code: Alles auswählen
players = Players.load_json("players.json")
players.add(Player("Peter", "Müller"))
players.save_json("more_players.json")
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 11:55
von schnickalot
danke Blackjack,
also instanziere ich Players über den return-Wert der Klassenmethode?! Macht man das generell so bei Klassen, wo man Daten einlädt?
Und in die JSON Datei fliesst ein Dict mit einem Key "players" und als Value eine Liste mit den Player-Objekten, die in to_dict() in JSON-Strings umgewandelt werden?! Hab ich das richtig verstanden?
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 12:09
von __deets__
Man macht das, weil man es ermoeglichen will, ein Players-Objekt auch so zu konstruieren. Ohne das da gleich ein dicker Lade-Mechanismus anspringt, fuer den man eine Menge an Randbedingungen erfuellen muss. So koennte man zB spaeter auch eine SQLite DB benutzen, oder die Klasse fuer Tests ohne alles instantiieren.
Und ich denke mal du hast das richtig verstanden. Denn so steht's ja auch in BJs code.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 12:10
von Sirius3
nein, von außen sieht es so aus, als ob Du eine Instanz der Klasse erzeugst, indem Du eine Klassenmethode aufrufst. Klassenmethoden helfen, wenn man verschiedene Arten hat, wie man Instanzen erzeugen könnte.
Das Umwandeln in einen String findet erst bei `json.dump` statt.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 13:07
von schnickalot
__blackjack__ hat geschrieben: Freitag 16. August 2019, 12:39
@schnickalot: Da muss man schon etwas mehr machen, denn das funktioniert ja nur wenn man ausschliesslich Werte hat, deren Typ vom JSON-Modul standardmässig in JSON-Werte umwandeln kann und nicht mit beliebigen Datentypen. Das JSON-Modul wüsste nicht was es beim Speichern mit einem `Player`-Objekt machen sollte. Und beim laden werden aus JSON-Daten auch nicht einfach so `Player`-Objekte. Da braucht man in beide Richtungen jeweils mindestens einen Zwischenschritt der Python-Objekte in eine ”JSON-kompatible” Struktur überführt die man dann speichern kann, und die JSON-Datenstruktur die man geladen hat, wieder in Python-Objekte.
Dann wird die "JSON-kompatible" Struktur in to_dict() erstellt? Wenn ja, wie muss das aussehen?
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 13:11
von Sirius3
`to_dict` muß ein Wörterbuch zurückliefern, dessen Schlüssel Strings sind und die Werte nur aus String, Int, Float, oder Listen und Wörterbücher, die diese Grunddatentypen enthalten, bestehen.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 13:26
von schnickalot
ok danke.. also muss jedes Attribut, welches Daten hat, die ich speichern möchte, in ein erneutes dict geschrieben werden?
Mal ganz billig so?:
Code: Alles auswählen
def to_dict(self):
dict = {}
dict["first_name"] = self.first_name
dict["last_name"] = self.last_name
dict["team"] = self.team.name
dict["games_won"] = self.games_won
dict["games_lost"] = self.games_lost
dict["frames_won"] = self.frames_won
dict["frames_lost"] = self.frames_lost
return dict
So sieht meine Player-Klasse aus:
Code: Alles auswählen
class Player:
def __init__(self, first_name, last_name, team):
self.first_name = first_name
self.last_name = last_name
self.team = team
self.score = 0
self.games_won = 0
self.games_lost = 0
self.frames_won = 0
self.frames_lost = 0
@property
def full_name(self):
return "{} {}".format(self.first_name, self.last_name)
@property
def games_total(self):
return self.games_won + self.games_lost
@property
def frames_total(self):
return self.frames_won + self.frames_lost
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 13:32
von __deets__
dict solltest du das nicht nennen, denn so heisst der Typ. Und ein bisschen umstaendlich machst du das auch. Einfacher:
Code: Alles auswählen
def to_dict(self):
return dict(
first_name=self.first_name,
frames_lost=self.frames_lost,
...
)
ACHTUNG: ich BENUTZE dict, den Typ, als Konstruktor. Einen Namen hat das Ding nicht.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 13:59
von schnickalot
ok prima. Vielen Dank.
zu from_dict():
Dann return ich dort cls(first_name, last_name, team, games_won=games_won, games_lost=games_lost... und weitere named args, die ich dann auch in der __init__ übergeben muss:
class Player:
def __init__(self, first_name, last_name, team, **kwargs)
richtig?
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 14:06
von Sirius3
Benutze kein **kwargs, sondern gib alle Argumente explizit an.
Re: Generelles Vorgehen beim Programmieren
Verfasst: Sonntag 18. August 2019, 14:11
von schnickalot

hatte ich schon so da stehen, dachte aber ist professioneller mit **kwargs.
Danke euch allen. Hab's langsam kapiert