Hallo, ich grübele seit geraumer Zeit über folgendem Problem: Ich möchte Instanzen einer Klasse aus einer Datei bilden - siehe nachfolgendes listing. Die Instanzen werden zwar gebildet, ich kann aber nicht auf sie zugreifen (NameError). Demgegenüber funktioniert der Zugriff, wenn ich die Instanzen manuell anlege oder über ein dictionary gehe. Ich würde aber gern direkt auf die Instanzen zugreifen. Kann mir bitte jemand erklären, was ich tun muss? Vielen Dank!
import csv
class Familienmitglieder(object):
def __init__(self, groesse, gewicht):
""" Klasse, deren Instanzen durch das Einlesen einer Datei kreiert werden sollen,
Diese Datei - familiendaten.txt - hat folgende:
Zeilenstruktur: katrin,180,85 (name, groesse, gewicht)"""
self.groesse = groesse
self.gewicht = gewicht
def einlesen(filename):
familiendaten: dict[str, Familienmitglieder] = {}
with open(filename, "r") as csvfile:
for row in csv.reader(csvfile):
familiendaten[row[0]] = Familienmitglieder(*row[1:])
return familiendaten
def main():
hugo = Familienmitglieder(185, 85.0)
print(hugo.groesse) # 185 - FUNKTIONIERT
familiendaten = einlesen("familiendaten.txt")
print(familiendaten["katrin"].groesse) # 180 - FUNKTIONIERT
print(katrin.groesse) # FUNKTIONIERT NICHT
if __name__ == '__main__':
main()
Instanzenbildung aus Datei
Wie du ja selbst siehst, wird "katrin" an keiner Stelle definiert. Also kann man darauf auch nicht zugreifen.
Tatsächlich hast du bereits den korrekten Weg gefunden: Die Daten gehören in eine geeignete Datenstruktur. Das Dictionary ist hier richtig.
Variablen dynamisch zu erzeugen ist zwar technisch möglich, aber unnötig schwer zu debuggen und im Code nicht nachvollziehbar.
Tatsächlich hast du bereits den korrekten Weg gefunden: Die Daten gehören in eine geeignete Datenstruktur. Das Dictionary ist hier richtig.
Variablen dynamisch zu erzeugen ist zwar technisch möglich, aber unnötig schwer zu debuggen und im Code nicht nachvollziehbar.
Code: Alles auswählen
hugo = Familienmitglieder(185, 85.0)
In "einlesen" erstellst du ein Dictionary und gibst das mit return zurück.
Wo genau denkst du denn, dass etwas an den Namen "katrin" gebunden wird?
Welches Problem möchtest du damit denn lösen?
Angenommen in der Datei stehen 200 Personendaten. Woher weißt du, wieviele es waren? Woher weißt du, welche Namen darin standen, damit du sie verwenden kannst?
Deshalb verwendet man die passende Datenstruktur. Zum Beispiel eine Liste. Oder ein Dicitonary, wenn es einen eindeutigen Schlüssel gibt, über den man auf den Wert zugreifen möchte.
Angenommen in der Datei stehen 200 Personendaten. Woher weißt du, wieviele es waren? Woher weißt du, welche Namen darin standen, damit du sie verwenden kannst?
Deshalb verwendet man die passende Datenstruktur. Zum Beispiel eine Liste. Oder ein Dicitonary, wenn es einen eindeutigen Schlüssel gibt, über den man auf den Wert zugreifen möchte.
Vielen Dank, das verstehe ich alles, auch Deine Argumente zum Nachvollzug von Datenstrukturen leuchten mir absolut ein. Das, was ich nicht verstehe ist: warum geht das technisch nicht? Ich würde gern verstehen, warum die Anlage von Instanzen aus einer Datei heraus nicht funktioniert. Noch besser wäre natürlich, wenn mir jemand sagen würde, wie es funktioniert.
Du erstellst doch beim Einlesen der Datei eine Instanz deiner Klasse. Mit `familiendaten["katrin"]` kannst du darauf zugreifen.hcshm hat geschrieben: Montag 24. Mai 2021, 16:48 Das, was ich nicht verstehe ist: warum geht das technisch nicht? Ich würde gern verstehen, warum die Anlage von Instanzen aus einer Datei heraus nicht funktioniert.
@hcshm: Doch, das geht technisch durchaus. Aber es zu tun macht keinen Sinn. Denn eben dafür sind Datenstrukturen da.
Deshalb habe ich dich gefragt, welches Problem du damit lösen möchtest.
Zum Code:
Der Name von der Klasse sollte im Singular stehen. Denn eine Instanz davon bildet ja nur ein Familienmitglied ab, nicht Familienmitglieder.
Klassen müssen spätestens seit Python 3 nicht mehr von "object" erben.
Wenn man eine Klasse nur als Datencontainer verwendet - in der Regel daran zu erkennen, dass sie nur aus einer __init__ bestehen - bietet es sich an, stattdessen einen Namedtuple zu verwenden. Allerdings nur, wenn man die Werte nicht verändern möchte.
Deshalb habe ich dich gefragt, welches Problem du damit lösen möchtest.
Zum Code:
Der Name von der Klasse sollte im Singular stehen. Denn eine Instanz davon bildet ja nur ein Familienmitglied ab, nicht Familienmitglieder.
Klassen müssen spätestens seit Python 3 nicht mehr von "object" erben.
Wenn man eine Klasse nur als Datencontainer verwendet - in der Regel daran zu erkennen, dass sie nur aus einer __init__ bestehen - bietet es sich an, stattdessen einen Namedtuple zu verwenden. Allerdings nur, wenn man die Werte nicht verändern möchte.
Code: Alles auswählen
>>> from collections import namedtuple
>>> Familienmitglied = namedtuple('Familienmitglied', ("groesse", "gewicht"))
>>> norma = Familienmitglied(164, 56)
>>> norma.groesse
164
>>> norma.gewicht
56
>>> # aber:
>>> norma.groesse = 155
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
norma.groesse = 155
AttributeError: can't set attribute
Zuletzt geändert von sparrow am Montag 24. Mai 2021, 17:01, insgesamt 1-mal geändert.
@hcshm: wie sparrow schon schrieb, lassen sich Namen zwar dynamisch anlegen, was aber zu erheblichen Wartungs- und Verständnisproblemen später führen kann und wird. Der saubere Weg führt in solchen Fällen über eine separate Datenstruktur. Statt
wäre das z.B.:
Auch so sind die Instanzen über die Namen direkt zugänglich und der globale Namensraum bleibt aufgeräumt.
Code: Alles auswählen
hugo = Familienmitglied(185, 85.0)
Code: Alles auswählen
familienmitglieder["Hugo"] = Familienmitglied("Hugo", 185, 85.0)
Vielen Dank, ja, aber ich greife doch über ein dictionary darauf zu. Warum kann ich nicht direkt zugreifen (bzw. wie kann ich direkt zugreifen)? Sorry vielmals für die Mühe und Gedanken, die ich den Experten mache. Irgendwie empfinde ich das dictionary als Umweg bzw. workaround. Und ich verstehe nicht, warum ich nicht direkt auf eine Instanz zugreifen kann, die ich durch das Einlesen einer Datei kreire.
@hcshm:
@kbr erklärt das richtig.
Das ist kein Umweg. Du greifst direkt auf die Instanz zu. Sie ist nur nicht an den Namen "hugo" sondern an 'familienmitglieder["Hugo"]' gebunden.
Das ist kein Workaround sondern dafür sind Datenstrukturen da.
@kbr erklärt das richtig.
Das ist kein Umweg. Du greifst direkt auf die Instanz zu. Sie ist nur nicht an den Namen "hugo" sondern an 'familienmitglieder["Hugo"]' gebunden.
Das ist kein Workaround sondern dafür sind Datenstrukturen da.
Du greifst doch direkt darauf zu. Das Dictionary ist kein Umweg, auch wenn du das glaubst.hcshm hat geschrieben: Montag 24. Mai 2021, 17:02 Und ich verstehe nicht, warum ich nicht direkt auf eine Instanz zugreifen kann, die ich durch das Einlesen einer Datei kreire.
Und wie würdest du dann dein Programm schreiben wollen?
Code: Alles auswählen
if katrin.groesse > 150 then
pass
Warum glaubst du, dass ein Dictionary oder eine Liste nicht ausreicht und du individuelle Namen benötigst? Zeig mal einen Anwendungsfall.
Vielen Dank, an /me and sparrow: Erstens bin ich jetzt von dem fixen Gedanken runter, dass irgendwas nicht gehen könnte. Zweitens kann ich alle Eure Erläuterungen in den letzten Beiträgen nachvollziehen. Erneut vielen Dank! Schöne Pfingsten noch!
Also... räusper... das, was Du ursprünglich vorhattest, das geht schon, aber wie meine verehrten Vorposter bereits geschrieben haben: es ergibt keinen Sinn, und der Trick, um das zu machen, ist nicht nur häßlich, sondern -- aus guten Gründen -- selten dokumentiert und eher unbekannt. Ich zeige die besagten Tricks daher nur zu rein informativen Zwecken und hoffe inständig, daß niemand ohne gute Gründe auf die Idee kommt, das jemals in realem Code zu nutzen...hcshm hat geschrieben: Montag 24. Mai 2021, 17:15 Vielen Dank, an /me and sparrow: Erstens bin ich jetzt von dem fixen Gedanken runter, dass irgendwas nicht gehen könnte. Zweitens kann ich alle Eure Erläuterungen in den letzten Beiträgen nachvollziehen. Erneut vielen Dank! Schöne Pfingsten noch!
Code: Alles auswählen
>>> a = 5
>>> globals()['a']
5
>>> globals()['a'] = 7
>>> a
7
>>> exec('a = 9')
>>> a
9
>>>
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import csv
from argparse import ArgumentParser
class NoMitgliedError(Exception): pass
class Mitglied:
'''vielleicht besser das dataclasses-Modul oder ein
collections.namedtuple benutzen...'''
def __init__(self, vorname, groesse, gewicht):
self.vorname = vorname
self.groesse = groesse
self.gewicht = gewicht
def __str__(self):
'''für die Ausgabe...'''
return f'{self.vorname} ist {self.groesse} cm groß und wiegt {self.gewicht} kg.'
class Familie:
mitglieder = list()
def read_csv(self, filename):
'''CSV-Datei mit Familienmitgliedern lesen'''
with open(filename, 'r') as ifh:
reader = csv.reader(ifh)
for line in reader:
mitglied = Mitglied(*line)
self.mitglieder.append(mitglied)
return self
def add(self, mitglied):
'''Jippie, ein neues Familienmitglied!'''
# hier besser isinstance(mitglied, Mitglied) prüfen...
# ... nicht daß sich ein Erbschleicher einschmuggelt!
self.mitglieder.append(mitglied)
def get(self, vorname):
'''Hole ein Familienmitglied per Vornamen, sauber!'''
for mitglied in self.mitglieder:
if mitglied.vorname == vorname:
return mitglied
return None
def __iter__(self):
'''über unsere Familienmitglieder iterieren'''
for mitglied in self.mitglieder:
yield mitglied
if __name__ == '__main__':
parser = ArgumentParser(description='Lies die Familienmitglieder')
parser.add_argument('filename', type=str, help='Datei mit den...')
args = parser.parse_args()
familie = Familie().read_csv(args.filename)
## geht
print('_________eins__________')
for mitglied in familie:
print(mitglied)
## geht
print('_________zwei__________')
karin = familie.get('karin')
print(karin)
@LukeNukem: dein Familien-Klasse kann nicht mehr, als ein simples Wörterbuch, kann also weg.
`read_csv` liefert dann auch besser ein Wörterbuch zurück. Benutze keine Klassenvariablen. So wie Dein Klassendesign jetzt ist, kann es nur eine Familie, also ein Exemplar dieser Klasse geben. Also ein weiterer Grund, das nicht so als Klasse zu schreiben.
Benutze keine kryptischen Abkürzungen. Was soll `ifh` bedeuten?
`read_csv` liefert dann auch besser ein Wörterbuch zurück. Benutze keine Klassenvariablen. So wie Dein Klassendesign jetzt ist, kann es nur eine Familie, also ein Exemplar dieser Klasse geben. Also ein weiterer Grund, das nicht so als Klasse zu schreiben.
Benutze keine kryptischen Abkürzungen. Was soll `ifh` bedeuten?
- __blackjack__
- User
- Beiträge: 14056
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Ergänzend: Das ``return self`` ist unpythonisch. Die Methode verändert das Objekt und gibt keinen erstellten sinnvollen Rückgabewert zurück. Die idiomatische Lösung wäre eine `classmethod()` die ein `Familie`-Objekt aus einer CSV-Datei erstellt.
Der Kommentar beim hinzufügen: Besser nicht den Typ prüfen, oder eine andere Programmiersprache verwenden, die nicht auf Duck Typing setzt.
Bei Textdateien sollte man immer die Kodierung angeben und bei CSV-Dateien noch das `newline`-Argument.
Ungetestet:
Problematisch finde ich das nirgends geprüft wird ob der Vorname mehr als einmal vorkommt, was dann dazu führen würde das Mitglieder nicht mit `get()` erreicht werden können.
Der Kommentar beim hinzufügen: Besser nicht den Typ prüfen, oder eine andere Programmiersprache verwenden, die nicht auf Duck Typing setzt.
Bei Textdateien sollte man immer die Kodierung angeben und bei CSV-Dateien noch das `newline`-Argument.
Ungetestet:
Code: Alles auswählen
class Familie:
def __init__(self):
self.mitglieder = list()
def __iter__(self):
"""über unsere Familienmitglieder iterieren"""
return iter(self.mitglieder)
def add(self, mitglied):
"""Jippie, ein neues Familienmitglied!"""
self.mitglieder.append(mitglied)
def get(self, vorname):
"""Hole ein Familienmitglied per Vornamen, sauber!"""
return next(
(mitglied for mitglied in self if mitglied.vorname == vorname),
None,
)
@classmethod
def read_csv(cls, filename):
"""CSV-Datei mit Familienmitgliedern lesen."""
result = cls()
with open(filename, "r", encoding="utf-8", newline="") as file:
for row in csv.reader(file):
result.add(Mitglied(*row))
return result
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Du hast in Deinem ganzen Leben noch keine zwanzig Zeilen lesbaren Python-Code implementiert, oder?Sirius3 hat geschrieben: Montag 24. Mai 2021, 19:48 @LukeNukem: dein Familien-Klasse kann nicht mehr, als ein simples Wörterbuch, kann also weg.
Kommentare lesen kannst Du also auch nicht...Sirius3 hat geschrieben: Montag 24. Mai 2021, 19:48 `read_csv` liefert dann auch besser ein Wörterbuch zurück. Benutze keine Klassenvariablen.

Das stimmt. Das ist auch genau so gewollt. Wenn ich das richtig sehe, wollte der TO auch nur eine Familie.Sirius3 hat geschrieben: Montag 24. Mai 2021, 19:48 So wie Dein Klassendesign jetzt ist, kann es nur eine Familie, also ein Exemplar dieser Klasse geben. Also ein weiterer Grund, das nicht so als Klasse zu schreiben.
Input File Handle. Meine Güte. Variablennamen.Sirius3 hat geschrieben: Montag 24. Mai 2021, 19:48 Benutze keine kryptischen Abkürzungen. Was soll `ifh` bedeuten?
Nein. Sonst würden numpy, scipy und opencv nicht sauber funktionieren, aber klar, die sind halt "unpythonisch".__blackjack__ hat geschrieben: Montag 24. Mai 2021, 23:46 Ergänzend: Das ``return self`` ist unpythonisch.
Aber natürlich. Das macht Python in seiner eigenen API ganz genauso... think .sort() vs. .sorted().__blackjack__ hat geschrieben: Montag 24. Mai 2021, 23:46 Die Methode verändert das Objekt und gibt keinen erstellten sinnvollen Rückgabewert zurück.
Ja, und? Dann mach' das doch mal sauber, wie Du Dir das vorstellst.__blackjack__ hat geschrieben: Montag 24. Mai 2021, 23:46 Die idiomatische Lösung wäre eine `classmethod()` die ein `Familie`-Objekt aus einer CSV-Datei erstellt.
Zeig' was Besseres. Kannst Du? Ach, nein. "return next()" ist voll anfängertauglich, weißt Du.__blackjack__ hat geschrieben: Montag 24. Mai 2021, 23:46 Der Kommentar beim hinzufügen: Besser nicht den Typ prüfen, oder eine andere Programmiersprache verwenden, die nicht auf Duck Typing setzt.
Ja, und beim Gesäßabwischen sollte man unbedingt spezifizieren, welches Material verwendet wird. Insbesondere bei Newbies!__blackjack__ hat geschrieben: Montag 24. Mai 2021, 23:46 Bei Textdateien sollte man immer die Kodierung angeben und bei CSV-Dateien noch das `newline`-Argument.
Schon lustig, daß Ihr zum Thema nichts beitragen vermögt und erst erwacht, wenn einer etwas erklärt. Richtige kleine Helden.
@LukeNukem: obwohl Du persönlich ausfällig wirst, möchte ich Dir nicht auf der selben Ebene antworten sondern sachlich bleiben.
Jede Sprache hat ihre Eigenheiten. Dass in Python einiges anders gemacht wird, als in Deiner gewohnten Programmiersprache, solltest Du annehmen, und nicht verärgert reagieren, wenn andere Leute Anmerkungen haben, die nicht gegen Dich als Person gerichtet sind, sondern allgemein allen Lesern hier im Forum helfen sollen, besseren Code zu schreiben.
Ja, in vielen Bibliotheken gibt es Abweichungen von idiomatischem Python. Meist gibt es dafür nachvollziehbare Gründe.
Das von Dir erwähnte list.sort gegenüber sorted ist ein gutes Beispiel. list.sort sortiert die Liste in-place, braucht also keinen Rückgabewert und liefert daher None zurück. sorted erzeugt eine neue sortierte Liste und muß folglich diese sortierte Liste zurückgeben.
Keine eigene Klasse zu verwenden, solange ein eingebauter Typ alles liefert, was man bisher braucht, hilft der Lesbarkeit. Dem familie.get seht man nicht an, ob jetzt hinter familie eine eigene Klasse oder ein Wörterbuch steckt, erspart aber 30 Zeilen zusätzlichen Code, den ich lesen muß.
Bei `familie = Familie().read_csv()` weiß der Leser nicht, ob read_csv nun eine neue Familie oder irgendeinen anderen Typ zurückliefert.
Bei
ist es dagegen klar, dass erst eine Familie erzeugt wird, und dann etwas in die Familie gelesen wird.
Will man es kürzer haben, benutzt man die von __blackjack__ erwähnte classmethod:
Ein wichtiges Grundprinzip beim Programmieren ist die Allgemeingültigkeit. Auch wenn man zu Anfang denkt, dass man nur eine Familie braucht, sollte man sein Programm nicht so schreiben, dass es von einer Klasse nur ein Exemplar geben darf. Denn das hat man längst vergessen, wenn dann doch die zweite Familie dazu kommt.
Hier noch die Beispielimplementierung:
Jede Sprache hat ihre Eigenheiten. Dass in Python einiges anders gemacht wird, als in Deiner gewohnten Programmiersprache, solltest Du annehmen, und nicht verärgert reagieren, wenn andere Leute Anmerkungen haben, die nicht gegen Dich als Person gerichtet sind, sondern allgemein allen Lesern hier im Forum helfen sollen, besseren Code zu schreiben.
Ja, in vielen Bibliotheken gibt es Abweichungen von idiomatischem Python. Meist gibt es dafür nachvollziehbare Gründe.
Das von Dir erwähnte list.sort gegenüber sorted ist ein gutes Beispiel. list.sort sortiert die Liste in-place, braucht also keinen Rückgabewert und liefert daher None zurück. sorted erzeugt eine neue sortierte Liste und muß folglich diese sortierte Liste zurückgeben.
Keine eigene Klasse zu verwenden, solange ein eingebauter Typ alles liefert, was man bisher braucht, hilft der Lesbarkeit. Dem familie.get seht man nicht an, ob jetzt hinter familie eine eigene Klasse oder ein Wörterbuch steckt, erspart aber 30 Zeilen zusätzlichen Code, den ich lesen muß.
Bei `familie = Familie().read_csv()` weiß der Leser nicht, ob read_csv nun eine neue Familie oder irgendeinen anderen Typ zurückliefert.
Bei
Code: Alles auswählen
familie = Familie()
familie.read_csv("familie.csv")
Will man es kürzer haben, benutzt man die von __blackjack__ erwähnte classmethod:
Code: Alles auswählen
familie = Familie.read_csv("familie.csv")
Hier noch die Beispielimplementierung:
Code: Alles auswählen
import csv
class Mitglied:
def __init__(self, vorname, groesse, gewicht):
self.vorname = vorname
self.groesse = groesse
self.gewicht = gewicht
def __str__(self):
return f'{self.vorname} ist {self.groesse} cm groß und wiegt {self.gewicht} kg.'
def read_family(filename):
'''CSV-Datei mit Familienmitgliedern lesen'''
result = {}
with open(filename, encoding="utf8", newline="") as input:
reader = csv.reader(input)
for entry in reader:
member = Mitglied(*entry)
result[member.vorname] = member
return result
def main():
familie = read_family("familie.csv")
for mitglied in familie.values():
print(mitglied)
karin = familie['karin']
print(karin)
if __name__ == '__main__':
main()