bahmady hat geschrieben: Dienstag 1. Juni 2021, 15:40
@LukeNukem
Ich kann meine Hausarbeit abgeben wann ich will. Diese Aufgabe ist die 4.) von 6 Aufgaben mit mehreren Aufgabenteilen. Ich wollte aber nächste Woche mit Datenbanken anfangen (ich mache ein Fernstudium). Ich habe nun mehrere Stunden alles Mögliche ausprobiert, habe in der Dokumentation für die format-Funktion reingeschaut, nichts klappt. Es wäre echt nett wenn du oder ein anderer mir einfach zeigen würde, wie ich die Format-Funktion hier richtig einsetzte. Ich habe soooo viel Zeit in dem kleinen Aufgabenteil investiert, da wird schon ein Lerneffekt da sein!
Najaaa... im Prinzip willst Du ja zwei Dateien lesen, deren Dateinamen auf der Kommandozeile übergeben werden sollen, dann für jede Zeile n die maximale Länge max([len(strings in n. Zeile der Dateien)]) ermitteln und daraus einen Formatstring bauen. Also eine Vorschrift, wie breit das betreffende Feld ausgegeben werden soll.
Der erste Schritt ist also, die Parameter zu parsen, die auf der Kommandozeile übergeben wurden. Wie für so vieles, hat Python dafür natürlich eine (eigentlich sogar mehrere) eingebaute Module, ich persönlich verwende dafür gerne das Modul "argparse", das die Klasse "ArgumentParser" bereitstellt -- der dann natürlich entsprechend konfiguriert werden muß. Und das könnte für Deinen Anwendungsfall etwa so aussehen:
Code: Alles auswählen
from argparse import ArgumentParser
if __name__ == '__main__':
parser = ArgumentParser(description='mimic paste(1)')
parser.add_argument('filenames', nargs='+', help='the files')
parser.add_argument('--serial', '-s', action='store_true', help='output as columns')
args = parser.parse_args()
Wenn Dein Programm mit "./paste.py -s names.txt numbers.txt" aufgerufen wurde, dann haben die Eigenschaften von "args" hinterher folgende Werte:
Code: Alles auswählen
args.filenames = ["names.txt", "numbers.txt"]
args.serial = True
Okay, wir haben jetzt eine Liste mit Dateinamen und wollen die Inhalte dieser Datein gerne in zeilenweise Listen einlesen:
Code: Alles auswählen
contents = list()
for filename in args.filenames:
with open(filename, 'r') as ifh: # diesen abgekürzten Variablennamen mögen manche hier nicht, ich schon ;-)
content = ifh.read().splitlines() # das entfernt im Gegensatz zu ifh.readlines() auch die Zeilenumbrüche
contents.append(content)
Unsere Variable "contents" enthält jetzt also eine Liste, deren einzelne Elemente wiederum Listen sind, also etwa so:
Code: Alles auswählen
contents = [
["Mark Smith", "Bobby Brown", ...],
["555-1234", "555-9876", ...]
]
Jetzt wollen wir natürlich zuallererste etwas machen, das jeder Autor immer, und zwar absolut immer, machen sollte: nämlich unsere Eingaben validieren, in diesem Falle also: sicherstellen, daß alle Elemente der Liste "contents" dieselbe Länge, hier also: dieselbe Anzahl an Elementen haben. Da gibt es verschiedene Möglichkeiten, aber wir legen uns zunächst eine Variable "linecount" an, die wir später wiederverwenden können, und überprüfen dann, ob unsere Dateien alle dieselbe Anzahl von Zeilen haben. Wenn nicht, werfen wir eine Exception mit einer schicken Fehlermeldung und steigen aus:
Code: Alles auswählen
linecount = len(contents[0])
if not all(len(content) == linecount for content in contents[1:]):
raise ValueError('Files do not all have the same linecount')
... und ansonsten: weitermachen. Also, wir haben unsere Dateiinhalte jetzt in hübschen Listen, also... sagen wir so: es gibt eine einfache und etwas... unelegante Variante und eine kurze und knackige die ich hier mal beide zeige. Zunächst bauen wir uns aus unseren "contents" aber erstmal eine schicke neue Datenstruktur, die uns das alles deutlich vereinfacht:
Dieser Codeabschnitt ist ein bisschen tricky, daher erkläre ich ihn kurz. Also, mit "*contents" machen wir aus unserer "contents"-Variablen eine Parameterliste (ich weiß nicht, wie ich das besser erklären könnte, vielleicht hat einer der geschätzten Mitleser ja eine bessere Idee). Dann verarbeiten wir sie mit der Builtin-Funktion "zip()" und wandeln deren Ergebnis (das ein zip-Objekt, also ein Generator ist) wieder in eine Liste um. Danach sieht unsere Variable "corresponding_lines" so aus -- die jeweils n-ten Zeilen aus jeder Eingabedatei sind jetzt zu einem "tuple()" zusammengefaßt und "corresponding_lines" eine Liste dieser Tupel.
Code: Alles auswählen
corresponding_lines = [('Mark Smith', '555-1234'), ('Bobby Brown', '555-9876'), ...]
Jetzt wollen wir die maximalen Längen jeder n-ten Zeile ermitteln, wie gesagt, das geht in kurz und knackig in einer Zeile mit zwei ineinander verschachtelten List Comprehensions und der Builtin-Funktion "max()":
Code: Alles auswählen
maxlengths = [max([len(string) for string in corresponding_lines[linenumber]]) for linenumber in range(len(corresponding_lines))]
oder in etwas ausführlicher und verständlicher:
Code: Alles auswählen
maxlengths = list()
for linenumber in range(len(corresponding_lines)):
maxlength = 0
for string in corresponding_lines[linenumber]:
length = len(string)
if length > maxlength:
maxlength = length
maxlengths.append(maxlength)
Das Ergebnis in "maxlengths" ist in beiden Fällen dasselbe:
Jetzt wollen wir uns aus diesen Maximallängen mal einen Formatstring zusammenbauen, auch dabei trickse ich wieder ein bisschen:
Code: Alles auswählen
formatstrings = ' '.join(['{:<%ds}'%(maxlength) for maxlength in maxlengths])
Der Trick, den ich mir hier zunutze mache, ist, das Python ganz verschiedene Formatierungsmöglichkeiten hat: die klassische, der C-Funktion "printf" ähnelnde Möglichkeit "<formatstring>%(parameter)", das wie die besagte C-Funktion mit "%<x>"-Platzhaltern arbeitet, wobei hier -- unsere "maxlengths" sind ja Ganzzahlen -- also mit "%d" arbeitet. Und dann gibt es die modernere Version, die mit "<formatstring>.format(parameter)" aufgerufen wird und "{}"-Platzhaltern arbeitet. (Es gibt noch eine ganz moderne Variante namens f-Strings, die der moderneren Variante ähnelt, hier aber mal außen vor bleiben soll. Bitte lies die von mir verlinkten Dokumentationen, das ist wirklich wichtig!)
Also, was haben wir jetzt? Genau: wir haben unsere Dateiinhalte in contents und einen Formatstring, also auf zur Ausgabe:
Code: Alles auswählen
for content in contents:
print(formatstring.format(*content))
Bitte, bitte, bitte spiel' mit dem Code ein bisschen herum, zieh' die List Comprehensions mal auseinander (siehe dazu mein Beispiel oben mit der langen und der kurzen Variante), und stell' sicher, daß Du meinen Code wirklich, wirklich verstehst -- die weiteren Python-Module werden sicherlich darauf aufbauen. Wenn Du Fragen hast, bitte stell' sie hier im Thread, okay? Zum Abschluß schreibe ich nochmal meinen ganzen Code zusammenhängend und habe dabei allerdings die überflüssige Variable "maxlengths" wegoptimiert, sondern konstruiere mir stattdessen direkt meinen Formatstring mit einem ".join()" auf eine "max()"-Funktion in einer List Comprehension, die auf einer List Comprehension arbeitet, welche wiederum in einer List Comprehension... genau. Viel Glück, Spaß und Erfolg!
Code: Alles auswählen
#!/usr/bin/env python
from argparse import ArgumentParser
if __name__ == '__main__':
parser = ArgumentParser(description='mimic paste(1)')
parser.add_argument('filenames', nargs='+', help='the files')
parser.add_argument('--serial', '-s', action='store_true', help='output as columns')
args = parser.parse_args()
contents = list()
for filename in args.filenames:
with open(filename, 'r') as ifh: # diesen abgekürzten Variablennamen mögen manche hier nicht, ich schon ;-)
content = ifh.read().splitlines() # das entfernt im Gegensatz zu ifh.readlines() auch die Zeilenumbrüche
contents.append(content)
# validate input(s)
linecount = len(contents[0])
if not all(len(content) == linecount for content in contents[1:]):
raise ValueError('Files do not all have the same linecount')
if args.serial:
# output files as lines
corresponding_lines = list(zip(*contents))
formatstring = ' '.join(
['{:<%ds}'%(maxlength) for maxlength in [
max([len(string) for string in corresponding_lines[linenumber]])
for linenumber in range(len(corresponding_lines))]])
for content in contents:
print(formatstring.format(*content))
else:
# @todo output files as columns
pass # this part is left as an excercise to the reader
PS: Oh, ach so... bitte schreib den "@todo"-Teil hinter dem letzten "else:" und zeig ihn uns mal -- dann können wir nämlich sehen, ob Du es halbwegs verstanden hast, und Dir vielleicht noch ein paar Tipps dazu geben.
