Guten Tag,
Ich arbeite nun schon eine Weile an einem Problem und komme nicht wirklich weiter. Wie gelingt es einem, bei einer text file die aus Namen sowie Zahlen in einer line besteht, nur die Zahlen zu wählen um mit ihnen zu rechnen?
Vielen Dank!
file filtern
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
Also die textFile besteht aus einem Nutzernamen gefolgt von float zahlen (werte) innerhalb einer line:
Nadia Baumann__4.7 7.5 6.5
Ich versuche nun nur mit den Zahlen zu rechnen und den Namen außer Acht zu lassen beim öffnen der file. Wie gelingt das?
Nadia Baumann__4.7 7.5 6.5
Ich versuche nun nur mit den Zahlen zu rechnen und den Namen außer Acht zu lassen beim öffnen der file. Wie gelingt das?
- __blackjack__
- User
- Beiträge: 14052
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@naomii2608: Programmieren ist Probleme in Teilprobleme zu zerlegen. Und die dann auch wieder in Teilprobleme. Solange bis die einzelnen Teilprobleme so klein sind, dass man sie mit einer Funktion mit wenigen Zeilen Code lösen kann. So eine Teillösung testet man dann. Und wenn sie funktioniert, schreibt man die nächste Teillösung. Bis man am Ende eine Gesamtlösung hat.
Eine Datei mit Zeilen zu verarbeiten kann man aufteilen in eine Zeile verarbeiten und diese Funktion die eine Zeile verarbeitet dann auf alle Zeilen anzuwenden.
Ein Teilproblem beim verarbeiten einer Zeile wäre hier zum Beispiel die Zeile in Bestandteile zu zerlegen und in Datentypen umzuwandeln mit denen man weiterarbeiten kann. Hier konkret aus "Nadia Baumann__4.7 7.5 6.5" ein eine Liste mit den Zahlen 4.7, 7.5, und 6.5 zu machen. Da muss man halt Code für schreiben der das Schritt für Schritt macht. Also die Zeichenkette in den Teil mit dem Namen und den Teil mit den Zahlen(darstellungen) aufteilen. Dann davon den hinteren Teil aufteilen so das man Zeichenketten mit den Zahldarstellungen hat. Und dann die Zeichenkettendarstellung jeder Zahl in eine Gleitkommazahl umwandeln. Das sind alles sehr grundlegende Operationen, die in jedem Python-Tutorial behandelt werden sollten. Zum Beispiel auch in dem Tutorial in der Python-Dokumentation.
Eine Datei mit Zeilen zu verarbeiten kann man aufteilen in eine Zeile verarbeiten und diese Funktion die eine Zeile verarbeitet dann auf alle Zeilen anzuwenden.
Ein Teilproblem beim verarbeiten einer Zeile wäre hier zum Beispiel die Zeile in Bestandteile zu zerlegen und in Datentypen umzuwandeln mit denen man weiterarbeiten kann. Hier konkret aus "Nadia Baumann__4.7 7.5 6.5" ein eine Liste mit den Zahlen 4.7, 7.5, und 6.5 zu machen. Da muss man halt Code für schreiben der das Schritt für Schritt macht. Also die Zeichenkette in den Teil mit dem Namen und den Teil mit den Zahlen(darstellungen) aufteilen. Dann davon den hinteren Teil aufteilen so das man Zeichenketten mit den Zahldarstellungen hat. Und dann die Zeichenkettendarstellung jeder Zahl in eine Gleitkommazahl umwandeln. Das sind alles sehr grundlegende Operationen, die in jedem Python-Tutorial behandelt werden sollten. Zum Beispiel auch in dem Tutorial in der Python-Dokumentation.
Code: Alles auswählen
...
print(parse_line("Nadia Baumann__4.7 7.5 6.5")) # → [4.7, 7.5, 6.5]
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
Also erstmal vielen Dank für die Antwort. Wo finde ich das tutorial in der Python Dokumentation? Das ist mein erster Beitrag hier, es kann sein, dass das Problem auch zu simpel ist für dieses Forum. Ich habe aber heute schon eine Weile nach tutorials gesucht und bin auf die Lösung gestoßen "sep = file.read (x-x)" zu verwenden, um das Zeichen in der Reihe zu bestimmen das angesprochen werden soll, was mir aber nicht richtig vorkommt.
def average_value(line):
numbers = line.split()
result = 0
for number in numbers:
result += float(number)
avg = result / 3
return avg
file = open("geo1.in.txt").read()
lines = file.splitlines()
for line in lines:
print(average_value(line))
Würde ich hier, vorher schon den gewünschten Teil der Zeile auswählen?
def average_value(line):
numbers = line.split()
result = 0
for number in numbers:
result += float(number)
avg = result / 3
return avg
file = open("geo1.in.txt").read()
lines = file.splitlines()
for line in lines:
print(average_value(line))
Würde ich hier, vorher schon den gewünschten Teil der Zeile auswählen?
- __blackjack__
- User
- Beiträge: 14052
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@naomii2608: Das Tutoral in der Python-Dokumentation heisst „Tutorial“ und ist gleich der zweite Teil der Dokumentation nach dem Teil der die Neuerungen in der jeweiligen Python-Version beschreibt. Also auf der Übersichtsseite der zweite Link auf im Hauptteil der Seite.
Dein Code führt ja zu einer Ausnahme:
Das ist ja recht deutlich was das Problem ist. Es reicht offensichtlich nicht einfach nur `split()` auf der Zeichenkette mit der Zeile aufzurufen. Und wenn man sich das Ergebnis von dem `split()` anschaut, sieht man auch, dass das die Zahlen nicht wirklich von dem Namen und den Unterstrichen trennt:
Es wäre sinnvoller erst den Namen von dem Teil mit den Zahlen abzutrennen, den den Teil kann man dann tatsächlich mit `split()` trennen.
Sonstige Anmerkungen zum Quelltext: Dateien sollte man schliessen. Am besten öffnet man Dateien wo möglich mit der ``with``-Anweisung.
Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben.
`file` ist ein passender Name für ein Dateiobjekt, aber kein passender Name für den Inhalt/Text aus einer Datei.
Man muss die Datei nicht komplett einlesen, man kann sie auch einfach zeilenweise abarbeiten. Dateiobjekte sind über die Zeilen in der Datei iterierbar. Selbst wenn man alle Zeilen in einer Liste braucht würde man nicht erst den gesamten Dateiinhalt als Zeichenkette einlesen und dann noch mal in einzelne Zeilen aufteilen. Denn wie gesagt ist ein Dateiobjekt über die Zeilen iterierbar, man kann also einfach `list()` mit dem Dateiobjekt als Argument aufrufen um die Zeilen in einer Liste zu sammeln.
Funktionsnamen werden üblicherweise nach der Tätigkeit benannt die sie ausführen. `average_value` ist keine Tätigkeit.
Namen sollten auch nicht abgekürzt werden. Wenn man `average` meint, sollte man nicht `avg` schreiben. Zumal man auch nicht jeden Zwischenwert an einen eigenen Namen binden muss, insbesondere wenn der dann nur sofort danach als Wert für ein ``return`` verwendet wird.
Die hart kodierte 3 ist nicht gut. Wenn in der Zeile mal mehr oder weniger als drei Werte sind, dann liefert die Funktion einen falschen Wert statt beispielsweise mit einer Ausnahme auf das Problem aufmerksam zu machen oder immer einen korrekten Wert, der zur tatsächlichen Anzahl passt, zu liefern.
Das arithmetische Mittel gibt es zudem bereits in der Standardbibliothek als Funktion.
Zwischenstand:
Dein Code führt ja zu einer Ausnahme:
Code: Alles auswählen
Traceback (most recent call last):
File "./forum18.py", line 16, in <module>
print(average_value(line))
File "./forum18.py", line 6, in average_value
result += float(number)
ValueError: could not convert string to float: 'Nadia'
Code: Alles auswählen
In [259]: "Nadia Baumann__4.7 7.5 6.5".split()
Out[259]: ['Nadia', 'Baumann__4.7', '7.5', '6.5']
Sonstige Anmerkungen zum Quelltext: Dateien sollte man schliessen. Am besten öffnet man Dateien wo möglich mit der ``with``-Anweisung.
Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben.
`file` ist ein passender Name für ein Dateiobjekt, aber kein passender Name für den Inhalt/Text aus einer Datei.
Man muss die Datei nicht komplett einlesen, man kann sie auch einfach zeilenweise abarbeiten. Dateiobjekte sind über die Zeilen in der Datei iterierbar. Selbst wenn man alle Zeilen in einer Liste braucht würde man nicht erst den gesamten Dateiinhalt als Zeichenkette einlesen und dann noch mal in einzelne Zeilen aufteilen. Denn wie gesagt ist ein Dateiobjekt über die Zeilen iterierbar, man kann also einfach `list()` mit dem Dateiobjekt als Argument aufrufen um die Zeilen in einer Liste zu sammeln.
Funktionsnamen werden üblicherweise nach der Tätigkeit benannt die sie ausführen. `average_value` ist keine Tätigkeit.
Namen sollten auch nicht abgekürzt werden. Wenn man `average` meint, sollte man nicht `avg` schreiben. Zumal man auch nicht jeden Zwischenwert an einen eigenen Namen binden muss, insbesondere wenn der dann nur sofort danach als Wert für ein ``return`` verwendet wird.
Die hart kodierte 3 ist nicht gut. Wenn in der Zeile mal mehr oder weniger als drei Werte sind, dann liefert die Funktion einen falschen Wert statt beispielsweise mit einer Ausnahme auf das Problem aufmerksam zu machen oder immer einen korrekten Wert, der zur tatsächlichen Anzahl passt, zu liefern.
Das arithmetische Mittel gibt es zudem bereits in der Standardbibliothek als Funktion.
Zwischenstand:
Code: Alles auswählen
#!/usr/bin/env python3
from statistics import mean
def calculate_average(line):
numbers = line.split() # FIXME
return mean(map(float, numbers))
def main():
with open("geo1.in.txt", encoding="utf-8") as lines:
for average in map(calculate_average, lines):
print(average)
if __name__ == "__main__":
main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
Das hat mir schon sehr geholfen. Ich habe jetzt text und Nummern trennen können:
file = open("geo1.in.txt").read()
lines = file.splitlines()
line = lines[0].split("_")
print(line[0])
print(line[10])
Für diese line: Tim Schnewil__________6.5 5.5 4.5
Ich frage mich aber warum es sich nur bei Index 10 trennen lässt?
file = open("geo1.in.txt").read()
lines = file.splitlines()
line = lines[0].split("_")
print(line[0])
print(line[10])
Für diese line: Tim Schnewil__________6.5 5.5 4.5
Ich frage mich aber warum es sich nur bei Index 10 trennen lässt?
Bitte schreib deinen Code zwischen Code-Tags. Die werden automatisch eingefügt, wenn du in "Vollständiger Editor & Vorschau" auf dem </> Button drückst. Dazwischen gehört dann dein Code.
Warum orientierst du dich nicht an dem Gerüst von __blackjack__?
Deine Aussage mit dem Trennen verstehe ich nicht. Mit .split("_") wird die Zeichenkette bei jedem Auftreten von "_" getrennt.
Warum orientierst du dich nicht an dem Gerüst von __blackjack__?
Deine Aussage mit dem Trennen verstehe ich nicht. Mit .split("_") wird die Zeichenkette bei jedem Auftreten von "_" getrennt.
- __blackjack__
- User
- Beiträge: 14052
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@naomii2608: Schönes Beispiel warum man das ganze Problem zeigen sollte und nicht nur Ausschnitte davon. Wenn wir nur Aussschnitte sehen, gehen wir halt davon aus, das der Rest genau so aussieht und treffen Annahmen die am Ende nicht zutreffen. Ich ging beispielsweise davon aus das zwischen Name und Zahlen in der Zeile zwei Unterstriche als Trenner sind. Nun sind bei dieser Zeile deutlich mehr Unterstriche. Und wenn man das mit der anderen Beispielzeile vergleicht, dann sieht das auch nicht nach einer fixen Länge aus.
Nun denn: Es gibt nicht nur `split()` zum trennen sondern auch noch andere Methoden die man hier verwenden könnte und die haben (teilweise) nicht nur das Trennzeichen als Argument.
Ansonsten noch der Hinweis doch einfach mal ein bisschen in einer interaktiven Python-Shell ”live” mit Codeteilen zu experimentieren wenn einem etwas nicht so ganz klar ist.
(Hinweis: Falls das jetzt verlockend aussehen sollte: `count()` ist *nicht* Teil einer Lösung. Das wäre eine andere Methode als `split()`, eventuell mit einem zusätzlichen Argument)
Nun denn: Es gibt nicht nur `split()` zum trennen sondern auch noch andere Methoden die man hier verwenden könnte und die haben (teilweise) nicht nur das Trennzeichen als Argument.
Ansonsten noch der Hinweis doch einfach mal ein bisschen in einer interaktiven Python-Shell ”live” mit Codeteilen zu experimentieren wenn einem etwas nicht so ganz klar ist.
Code: Alles auswählen
In [295]: line = "Tim Schnewil__________6.5 5.5 4.5"
In [296]: line.split("_")
Out[296]: ['Tim Schnewil', '', '', '', '', '', '', '', '', '', '6.5 5.5 4.5']
In [297]: line.split("_").count("")
Out[297]: 9
In [298]: line.count("_")
Out[298]: 10
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
Ja das stimmt. Also hier einmal die volle text.file für den Überblick:
Tim Schnewil__________6.5 5.5 4.5
Nadia Baumann________6.7 7.5 7.7
Thomas Bring____6.8 7.5 7.3
Sabine Prentel____1.5 5.2 7.9
Sam Moor_________2.5 4.5 6.7
Ich bin es jetzt so:
Tim Schnewil__________6.5 5.5 4.5
Nadia Baumann________6.7 7.5 7.7
Thomas Bring____6.8 7.5 7.3
Sabine Prentel____1.5 5.2 7.9
Sam Moor_________2.5 4.5 6.7
Ich bin es jetzt so:
Code: Alles auswählen
def line_student(student):
student_details = student.split("__________")
full_name = student_details[0]
geography_grades = student_details[1]
def print_grades(input_grades):
geography_grades = input_grades.split(" ")
first_test = float(geography_grades[0])
second_test = float(geography_grades[1])
third_test = float(geography_grades[2])
from statistics import mean
def calculate_average(line):
numbers = first test, second test, third test
return mean(map(float, numbers))
/code]
Der Grund, weshalb ich das Gerüst nicht übernommen habe: encoding="utf-8" > den Teil habe ich nicht verstanden
Wer hat sich denn diesen Wahnsinn ausgedacht? Ich koennte ja verstehen, wenn man mit _ auspolstert, um auf eine bestimmte Laenge zu kommen. Aber das ist ja alles Kraut und Rueben 
Das encoding ist wichtig wenn es Umlaute geben kann. Dann muss das passen. Hast du Beispiele fuer Namen mit Umlauten darin?

Das encoding ist wichtig wenn es Umlaute geben kann. Dann muss das passen. Hast du Beispiele fuer Namen mit Umlauten darin?
Die Fragestellung muß passend definiert werden, und die heißt, Du willst an beliebig vielen _ trennen. Das schreit förmlich nach regulären Ausdrücken, oder
Code: Alles auswählen
fullname, grades = line.rsplit('_', 1)
fullname = fullname.rstrip('_')
grades = list(map(float, grades.split()))
- __blackjack__
- User
- Beiträge: 14052
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Ergänzend zu dem was __deets__ zur Kodierung gesagt hat: Ich fände das auch wichtig wenn in den aktuellen Daten keine Umlaute enthalten sind, denn dann würde ich als Kodierung ASCII nehmen, damit das Programm laut und deutlich auf die Fresse fällt, wenn da irgendwann einmal doch jemand etwas mit Umlauten rein füttert.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
Es sind zum Glück keine Umlaute enthalten.
-
- User
- Beiträge: 11
- Registriert: Dienstag 16. Juni 2020, 18:00
]Hallo, ich habe es jetzt hinbekommen mit viel Recherche. Es geht darum am Ende einen Ordner mit den Lösungen zu präsentieren.
Meine Frage wäre nur, ob es noch einfacher geht?
Meine Frage wäre nur, ob es noch einfacher geht?
Code: Alles auswählen
TESTS = 3
def grades_and_names():
grades_list = []
for i in open('grades.txt').readlines():
if i.split('_'):
group_student_data = list()
group_student_data.append(i.split('_')[0])
group_student_data.append(i.split('_')[-1][:-1])
grades_list.append(group_student_data)
else:
continue
return grades_list
def calculate_average(grades):
results = 0.0
for grade in grades.split(' '):
results += float(grade)
average = results / TESTS
return average
def format_data(names_and_grades):
f = open('grades.txt', 'w')
f.write('Report \n')
for student in names_and_grades:
average_grade = calculate_average(student[1])
new = '%s has an average grade of %s\n' % (student[0], round(average_grade, 1))
f.write(new)
f.write('End of report\n')
f.close()
student_data = grades_and_names()
format_data(student_data)
Funktionen sollte nach Tätigkeiten benannt werden `grades_and_names` ist das nicht. `read_names_and_grades` wäre besser, zumal ja Namen und Noten und nicht Noten und Namen gelesen werden.
Dateien, die man öffnet sollte man auch wieder schließen, am besten mit dem with-Statement.
`i` ist ein besonders schlechter Name für eine Zeile. Fileobjekte sind bereits Iteratoren über die Zeilen, ein `readlines` ist also überflüssig.
Listen mit fixen Elementen erstellt man mit eckigen Klammern. Zwei mal split auf den selben String anzuwenden ist Verschwendung. Ein `else: continue` ist nicht nur überflüssig, sondern auch doof, falls man die Schleife doch mal erweitert.
In Variablennamen sollten keine Typen vorkommen. Um den Durchschnitt zu berechnen, gibt es bereits die Funktion statistics.mean.
Es ist doof, die Inputdatei mit anderem Inhalt zu überschreiben. Beim Öffnen von Textdateien muß man immer ein Encoding angeben, am besten das richtige. Statt magischer Indexwerte benutzt man Tuple-unpacking. Runden macht man mit Formatangaben, nicht mit round.
Es sollte keine globalen Variablen geben, daher müssen auch die letzten beiden Zeilen in eine Funktion, die üblicherweise main genannt wird.
Dateien, die man öffnet sollte man auch wieder schließen, am besten mit dem with-Statement.
`i` ist ein besonders schlechter Name für eine Zeile. Fileobjekte sind bereits Iteratoren über die Zeilen, ein `readlines` ist also überflüssig.
Listen mit fixen Elementen erstellt man mit eckigen Klammern. Zwei mal split auf den selben String anzuwenden ist Verschwendung. Ein `else: continue` ist nicht nur überflüssig, sondern auch doof, falls man die Schleife doch mal erweitert.
In Variablennamen sollten keine Typen vorkommen. Um den Durchschnitt zu berechnen, gibt es bereits die Funktion statistics.mean.
Es ist doof, die Inputdatei mit anderem Inhalt zu überschreiben. Beim Öffnen von Textdateien muß man immer ein Encoding angeben, am besten das richtige. Statt magischer Indexwerte benutzt man Tuple-unpacking. Runden macht man mit Formatangaben, nicht mit round.
Es sollte keine globalen Variablen geben, daher müssen auch die letzten beiden Zeilen in eine Funktion, die üblicherweise main genannt wird.
Code: Alles auswählen
from statistics import mean
GRADES_FILENAME = 'grades.txt'
def read_names_and_grades():
names_and_grades = []
with open(GRADES_FILENAME, encoding="utf8") as lines:
for line in lines:
name, grades = line.rsplit('_', 1)
names_and_grades.append((name.rstrip('_'), grades))
return names_and_grades
def calculate_average(grades):
return mean(map(float, grades.split()))
def format_data(names_and_grades):
with open(GRADES_FILENAME, 'w', encoding="utf8") as output:
output.write('Report\n')
for name, grades in names_and_grades:
average_grade = calculate_average(grades)
output.write(f'{name} has an average grade of {grades:.1f}\n')
output.write('End of report\n')
def main():
names_and_grades = read_names_and_grades()
format_data(names_and_grades)
if __name__ == '__main__':
main()
- __blackjack__
- User
- Beiträge: 14052
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@naomii2608: Ich hatte es ja schon mal geschrieben: Dateien die man öffnet sollte man auch wieder schliessen. Am besten verwendet man wo es möglich ist die ``with``-Anweisung um das auch in jedem Fall sicherzustellen.
`grades_and_names` beschreibt keine Tätigkeit. Das wäre ein passender Name für das Ergebnis der Funktion den man aber nicht mehr verwenden kann, weil die Funktion ja schon so heisst. Wobei es umgekehrt richtiger wäre, denn die Daten enthalten ja erst den Namen und dann die Noten. Beim Argument von `format_data()` wird es dann ja richtig herum geschrieben.
Grunddatentypen gehören nicht in Namen. Die `grade_list` ist dann einfach nur `grades` oder eben `names_and_grades`.
Der `readlines()`-Aufruf ist überflüssig. Man kann über das Dateiobjekt itereren und bekommt auch so die Zeilen, ohne dabei gleich die gesamte Datei in den Speicher zu laden.
`i` ist ein verdammt schlechter Name für eine Zeile. In einer Schleife als Laufvariable nochmal schlechter weil *jeder* hier eine ganze Zahl hinter dem Namen vermutet.
In der Schleife wird dann 3× die gleiche Zeichenkette mit dem gleichen Trenner zerlegt. Das macht man nur *einmal* und merkt sich den immer gleichen Wert der dabei heraus kommt.
Das ``else: continue`` macht keinen Sinn. Das kann man ersatzlos streichen ohne das sich etwas am Programmablauf ändert. Mit ``continue`` sollte man sowieso sehr sparsam umgehen. Man kann Code eigentlich immer so strukturieren, dass man das nicht braucht.
Erst eine leere Liste anzulegen um gleich danach dann in zwei Anweisungen da per `append()` Elemente anzuhängen ist unnötig umständlich. Python hat Syntax um literale Listen mit Elementen zu schreiben, und dann kann man sich den Namen für das Zwischenergebnis auch gleich sparen:
Das entfernen des Zeilenendezeichens über ``[:-1]`` ist gefährlich weil die letzte Zeile einer Textdatei das gar nicht zwingend haben muss. Es ist auch gar nicht nötig weil `float()` problemlos mit „whitespace“ vor und hinter der Zahldarstellung klar kommt:
Für das Zusammenfassen von Daten mit unterschiedlichen Bedeutungen je Index nimmt man Tupel und keine Listen.
Das beim schreiben der gleiche Dateiname verwendet wird ist blöd für's testen, weil man sich damit ja immer die Eingabedaten für den nächsten Testlauf überschreibt und deshalb vor jedem Testlauf die Eingabedaten erst wieder herstellen muss.
Auch beim Schreiben gilt wieder: ``with`` verwenden.
`f` ist kein guter Name. `file` würde viel besser aussagen was das ist.
Der Name `format_data()` triffts auch nicht wirklich gut, denn da werden die Daten nicht nur formatiert sondern überhaupt erst mal verarbeitet und dann auch noch in eine Datei geschrieben.
Statt jedes Element an den einen Namen `student` zu binden und dann im Schleifenkörper über ”magische” Indexwerte auf die beiden Bestandteile zuzugreifen, würde man das besser gleich beim ``for`` an zwei aussagekräftige Namen binden.
Mindestens mal das Zwischenergebnis `new` braucht man nicht an einen Namen binden, das kann man auch gleich einsetzen. `new` ist ja auch supernichtssagend.
Den ``%``-Operator würde ich nicht mehr zur Zeichenkettenformatierung verwenden. Es gibt die `format()`-Methode und noch besser f-Zeichenkettenliterale.
`round()` verwendet man nicht um die Darstellung auf eine Anzahl von Nachkommastellen zu berechnen. Eventuell wärst Du auch ein bisschen überrascht *wie* `round()` arbeitet:
Sind das die von Dir erwarteten Ergebnisse?
Du willst statt `round()` die Zeichenkettenformatierung dafür verwenden.
In `calculate_average()` muss man `average` nicht haben. Ein Ergebnis das gleich in der nächsten Zeile mit ``return`` zurückgegeben wird, braucht man nicht an einen Namen binden.
`results` würde besser `total` heissen. Auf jeden Fall nicht Mehrzahl, denn das ist ja nur ein skalarer Wert. Und `result` stimmt nicht, weil es nicht das Ergebnis ist.
`TESTS` ist auch ein etwas irreführender Name, denn dahinter verbirgt sich kein Container-Objekt das Tests beinhaltet sondern die Anzahl der Tests. Aber ich glaube das hatte ich auch schon geschrieben: das sollte man nicht einfach so vorgeben und annehmen, weil sonst bei falschen Eingabedaten mit mehr oder weniger als drei Noten einfach ein falsches Ergebnis berechnet wird, ohne dass das jemandem auffallen muss. Diese Zahl sollte der jeweiligen *tatsächlichen* Anzahl der Noten entsprechen.
Statt an " " zu teilen würde man hier das Argument von `split()` einfach weg lassen. Das ist robuster.
Kleiner Zwischenstand:
Die Aufteilung der Aufgaben auf die Funktionen ist aber eher schlecht. Daten sollten an der Stelle wo sie das Programm betreten in eine Form gebacht werden in der sie weiterverbeitet werden können. Aus einer Zeile sollte ein Paar aus Name und Noten werden, wobei die Noten eine Liste aus Zahlen sein sollten und keine Zeichenkette die erst weiter hinten in der Verarbeitung in Zahlen umgewandelt werden. Von einer Funktion die einen Durchschnitt ausrechnet erwartet jeder das man da die Zahlen übergibt, und keine Zeichenkette wo die Zahlen erst noch heraus gelesen werden müssen.
Die `calculate_average()`-Funktion wird auch supersimpel wenn man die Umwandlung von einer Zeichenkette in Zahlen da raus lässt, denn dann braucht man keine Schleife mehr, sondern kann einfach die `sum()`-Funktion verwenden.
Und am Namen `process_and_save_names_and_grades()` sieht man, dass die Funktion vielleicht zu viel macht. Übliche ”Trennlinien” sind die Eingabe der Daten in das Programm, die Verarbeitung, und die Ausgabe. Das kann man dann alles drei unabhängig voneinander Testen. Man kann Testdaten laden ohne das da was verarbeitet oder gespeichert werden muss. Man kann die Verarbeitung testen ohne Daten aus Dateien zu laden oder in Dateien zu speichern. Und man kann das schreiben von Daten in Dateien testen, ohne vorher Daten aus einer Datei geladen und verarbeitet zu haben. Das hat dann auch zur Folge, dass man Ein- und Ausgabe leicht tauschen oder wählbar machen kann. Denn der Verarbeitung ist es ja egal ob die Daten aus einer Datei kommen und welches Format die hat, oder aus einer Datenbank, oder einem Webdienst, und so weiter. Umgekehrt ist es für Eingabe und Verarbeitung auch egal wo die Daten am Ende hingeschrieben werden. Ob auf dem Bildschirm ausgegeben, in einer Datei (Text, PDF, als Diagramm in einem Bild, …), in einer Datenbank gespeichert, und so weiter.
Um die Funktionen flexibler zu machen sollten die Dateinamen Argumente ein. Dann kann man aus verschiedenen Dateien laden und in verschiedene Dateien speichern. Tests mit Testdaten werden so einfacher.
Nächster Zwischenstand mit einer sinnvolleren Aufteilung des Codes auf verschiedene Funktionen:
Jetzt kann man mal die Schleifen angehen und schauen aus welchen man einfach und sinnvoll „list comprehensions“ oder Generatorausdrücke machen kann:
`grades_and_names` beschreibt keine Tätigkeit. Das wäre ein passender Name für das Ergebnis der Funktion den man aber nicht mehr verwenden kann, weil die Funktion ja schon so heisst. Wobei es umgekehrt richtiger wäre, denn die Daten enthalten ja erst den Namen und dann die Noten. Beim Argument von `format_data()` wird es dann ja richtig herum geschrieben.
Grunddatentypen gehören nicht in Namen. Die `grade_list` ist dann einfach nur `grades` oder eben `names_and_grades`.
Der `readlines()`-Aufruf ist überflüssig. Man kann über das Dateiobjekt itereren und bekommt auch so die Zeilen, ohne dabei gleich die gesamte Datei in den Speicher zu laden.
`i` ist ein verdammt schlechter Name für eine Zeile. In einer Schleife als Laufvariable nochmal schlechter weil *jeder* hier eine ganze Zahl hinter dem Namen vermutet.
In der Schleife wird dann 3× die gleiche Zeichenkette mit dem gleichen Trenner zerlegt. Das macht man nur *einmal* und merkt sich den immer gleichen Wert der dabei heraus kommt.
Das ``else: continue`` macht keinen Sinn. Das kann man ersatzlos streichen ohne das sich etwas am Programmablauf ändert. Mit ``continue`` sollte man sowieso sehr sparsam umgehen. Man kann Code eigentlich immer so strukturieren, dass man das nicht braucht.
Erst eine leere Liste anzulegen um gleich danach dann in zwei Anweisungen da per `append()` Elemente anzuhängen ist unnötig umständlich. Python hat Syntax um literale Listen mit Elementen zu schreiben, und dann kann man sich den Namen für das Zwischenergebnis auch gleich sparen:
Code: Alles auswählen
group_student_data = list()
group_student_data.append(parts[0])
group_student_data.append(parts[-1][:-1])
names_and_grades.append(group_student_data)
# =>
group_student_data = [parts[0], parts[-1][:-1]]
names_and_grades.append(group_student_data)
# =>
names_and_grades.append([parts[0], parts[-1][:-1]])
Code: Alles auswählen
In [369]: float(" 42.23 \n")
Out[369]: 42.23
Das beim schreiben der gleiche Dateiname verwendet wird ist blöd für's testen, weil man sich damit ja immer die Eingabedaten für den nächsten Testlauf überschreibt und deshalb vor jedem Testlauf die Eingabedaten erst wieder herstellen muss.
Auch beim Schreiben gilt wieder: ``with`` verwenden.
`f` ist kein guter Name. `file` würde viel besser aussagen was das ist.
Der Name `format_data()` triffts auch nicht wirklich gut, denn da werden die Daten nicht nur formatiert sondern überhaupt erst mal verarbeitet und dann auch noch in eine Datei geschrieben.
Statt jedes Element an den einen Namen `student` zu binden und dann im Schleifenkörper über ”magische” Indexwerte auf die beiden Bestandteile zuzugreifen, würde man das besser gleich beim ``for`` an zwei aussagekräftige Namen binden.
Mindestens mal das Zwischenergebnis `new` braucht man nicht an einen Namen binden, das kann man auch gleich einsetzen. `new` ist ja auch supernichtssagend.
Den ``%``-Operator würde ich nicht mehr zur Zeichenkettenformatierung verwenden. Es gibt die `format()`-Methode und noch besser f-Zeichenkettenliterale.
`round()` verwendet man nicht um die Darstellung auf eine Anzahl von Nachkommastellen zu berechnen. Eventuell wärst Du auch ein bisschen überrascht *wie* `round()` arbeitet:
Code: Alles auswählen
In [372]: round(5.55, 1)
Out[372]: 5.5
In [373]: round(5.45, 1)
Out[373]: 5.5
In [374]: round(5.35, 1)
Out[374]: 5.3
In [375]: round(5.25, 1)
Out[375]: 5.2
In [376]: round(5.15, 1)
Out[376]: 5.2
Du willst statt `round()` die Zeichenkettenformatierung dafür verwenden.
In `calculate_average()` muss man `average` nicht haben. Ein Ergebnis das gleich in der nächsten Zeile mit ``return`` zurückgegeben wird, braucht man nicht an einen Namen binden.
`results` würde besser `total` heissen. Auf jeden Fall nicht Mehrzahl, denn das ist ja nur ein skalarer Wert. Und `result` stimmt nicht, weil es nicht das Ergebnis ist.
`TESTS` ist auch ein etwas irreführender Name, denn dahinter verbirgt sich kein Container-Objekt das Tests beinhaltet sondern die Anzahl der Tests. Aber ich glaube das hatte ich auch schon geschrieben: das sollte man nicht einfach so vorgeben und annehmen, weil sonst bei falschen Eingabedaten mit mehr oder weniger als drei Noten einfach ein falsches Ergebnis berechnet wird, ohne dass das jemandem auffallen muss. Diese Zahl sollte der jeweiligen *tatsächlichen* Anzahl der Noten entsprechen.
Statt an " " zu teilen würde man hier das Argument von `split()` einfach weg lassen. Das ist robuster.
Kleiner Zwischenstand:
Code: Alles auswählen
#!/usr/bin/env python3
def calculate_average(grades):
grades = grades.split()
total = 0
for grade in grades:
total += float(grade)
return total / len(grades)
def load_names_and_grades():
with open("grades.txt", encoding="ascii") as lines:
names_and_grades = []
for line in lines:
parts = line.split("_")
if parts:
names_and_grades.append([parts[0], parts[-1]])
return names_and_grades
def process_and_save_names_and_grades(names_and_grades):
with open("grades_2.txt", "w", encoding="ascii") as file:
file.write("Report.\n")
for name, grades in names_and_grades:
average_grade = calculate_average(grades)
file.write(
f"{name} has an average grade of {average_grade:.1f}.\n"
)
file.write("End of report.\n")
def main():
process_and_save_names_and_grades(load_names_and_grades())
if __name__ == "__main__":
main()
Die `calculate_average()`-Funktion wird auch supersimpel wenn man die Umwandlung von einer Zeichenkette in Zahlen da raus lässt, denn dann braucht man keine Schleife mehr, sondern kann einfach die `sum()`-Funktion verwenden.
Und am Namen `process_and_save_names_and_grades()` sieht man, dass die Funktion vielleicht zu viel macht. Übliche ”Trennlinien” sind die Eingabe der Daten in das Programm, die Verarbeitung, und die Ausgabe. Das kann man dann alles drei unabhängig voneinander Testen. Man kann Testdaten laden ohne das da was verarbeitet oder gespeichert werden muss. Man kann die Verarbeitung testen ohne Daten aus Dateien zu laden oder in Dateien zu speichern. Und man kann das schreiben von Daten in Dateien testen, ohne vorher Daten aus einer Datei geladen und verarbeitet zu haben. Das hat dann auch zur Folge, dass man Ein- und Ausgabe leicht tauschen oder wählbar machen kann. Denn der Verarbeitung ist es ja egal ob die Daten aus einer Datei kommen und welches Format die hat, oder aus einer Datenbank, oder einem Webdienst, und so weiter. Umgekehrt ist es für Eingabe und Verarbeitung auch egal wo die Daten am Ende hingeschrieben werden. Ob auf dem Bildschirm ausgegeben, in einer Datei (Text, PDF, als Diagramm in einem Bild, …), in einer Datenbank gespeichert, und so weiter.
Um die Funktionen flexibler zu machen sollten die Dateinamen Argumente ein. Dann kann man aus verschiedenen Dateien laden und in verschiedene Dateien speichern. Tests mit Testdaten werden so einfacher.
Nächster Zwischenstand mit einer sinnvolleren Aufteilung des Codes auf verschiedene Funktionen:
Code: Alles auswählen
#!/usr/bin/env python3
def calculate_average(grades):
return sum(grades) / len(grades)
def load_names_and_grades(filename):
with open(filename, encoding="ascii") as lines:
names_and_grades = []
for line in lines:
parts = line.split("_")
if parts:
name = parts[0]
grades = list()
for grade in parts[-1].split():
grades.append(float(grade))
names_and_grades.append((name, grades))
return names_and_grades
def calculate_averages(names_and_grades):
names_and_averages = list()
for name, grades in names_and_grades:
names_and_averages.append((name, calculate_average(grades)))
return names_and_averages
def save_report(filename, names_and_averages):
with open(filename, "w", encoding="ascii") as file:
file.write("Report.\n")
for name, average_grade in names_and_averages:
file.write(
f"{name} has an average grade of {average_grade:.1f}.\n"
)
file.write("End of report.\n")
def main():
save_report(
"grades_2.txt", calculate_averages(load_names_and_grades("grades.txt"))
)
if __name__ == "__main__":
main()
Code: Alles auswählen
#!/usr/bin/env python3
def calculate_average(grades):
return sum(grades) / len(grades)
def load_names_and_grades(filename):
with open(filename, encoding="ascii") as lines:
names_and_grades = []
for line in lines:
parts = line.split("_")
if parts:
names_and_grades.append(
(parts[0], [float(grade) for grade in parts[-1].split()])
)
return names_and_grades
def calculate_averages(names_and_grades):
return [
(name, calculate_average(grades)) for name, grades in names_and_grades
]
def save_report(filename, names_and_averages):
with open(filename, "w", encoding="ascii") as file:
file.write("Report.\n")
file.writelines(
f"{name} has an average grade of {average_grade:.1f}.\n"
for name, average_grade in names_and_averages
)
file.write("End of report.\n")
def main():
save_report(
"grades_2.txt", calculate_averages(load_names_and_grades("grades.txt"))
)
if __name__ == "__main__":
main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari