8 – Ein- und Ausgabe

Einführung in Python und PsychoPy

Autor

Clemens Brunner

Veröffentlicht

15. Mai 2023

Allgemeines

Unter Eingabe (Input) versteht man die Eingabe von Informationen von der Tastatur bzw. aus einer Datei, um diese in einem Programm verarbeiten zu können. Unter Ausgabe versteht man die Ausgabe von Informationen am Bildschirm bzw. in eine Datei (also z.B. Ergebnisse einer Berechnung). Im Folgenden werden wir sehen, wie wir Strings für die Ausgabe formatieren können und wie wir Textdateien lesen bzw. schreiben können.

Ausgabe von Strings

Strings verketten

Wir haben bereits die print-Funktion kennengelernt, welche Strings (und andere Objekte) als Text am Bildschirm ausgeben kann. Im einfachsten Fall kann so ein String ausgegeben werden:

print("Hallo")
Hallo

Auch Zahlen kann man als Argument für print verwenden:

print(55)
55

Mit der Funktion str kann man beliebige Objekte (also auch z.B. Ganzzahlen) in einen String umwandeln:

str(55)
'55'

Die print-Funktion kann beliebig viele Argumente entgegennehmen. Sie gibt dann alle Argumente, getrennt von Leerzeichen, am Bildschirm aus.

a = 5
b = 10
print(a, "+", b, "=", a + b)  # fünf Argumente
5 + 10 = 15

Wie wir bereits wissen, kann man mehrere Strings mit + verketten. Daher könnte man obige Ausgabe auch durch Ausgabe eines einzigen Strings durch Verketten von einzelnen Strings erzeugen:

print(str(a) + " + " + str(b) + " = " + str(a + b))  # ein Argument
5 + 10 = 15

f-Strings

Sogenannte f-Strings sind ganz normale Strings, man kann aber bei ihrer Erzeugung beliebige Ausdrücke verwenden, die dann ausgewertet in den String eingesetzt werden. Man definiert einen f-String genau wie einen normalen String mittels Anführungszeichen, stellt aber ein f voran, also z.B.:

f"Hallo"
'Hallo'

Sinnvoll sind f-Strings nur dann, wenn man auch tatsächlich Ausdrücke integriert, welche innerhalb von geschwungenen Klammern {} angegeben werden. Alle Ausdrücke werden ausgewertet und in den String eingesetzt (interpoliert):

name = "World"
f"Hello {name}!"
'Hello World!'

Den fertigen String kann man dann einfach mit print am Bildschirm ausgeben. Das Beispiel aus dem vorigen Abschnitt kann man daher wie folgt anschreiben:

print(f"{a} + {b} = {a + b}")
5 + 10 = 15

Ausdrücke, wie z.B. Zahlen, können optional auch speziell formatiert werden. Sehen wir uns zunächst an, wie die Kreiszahl \(\pi\) standardmäßig ausgegeben wird:

import math

print(f"π ≈ {math.pi}")
π ≈ 3.141592653589793

Mit einem optionalen Doppelpunkt und einem sogenannten Format Specifier innerhalb der geschwungenen Klammern kann ein Ausdruck gezielt formatiert werden. Das folgende Beispiel formatiert die Kommazahl Pi so, dass sie mit drei Nachkommastellen ausgegeben wird:

print(f"π ≈ {math.pi:.3f}")
π ≈ 3.142

Oder mit 16 Nachkommastellen:

print(f"π ≈ {math.pi:.16f}")
π ≈ 3.1415926535897931

Die minimale Feldbreite kann mit einer Zahl direkt nach dem Doppelpunkt spezifiziert werden, im folgenden Beispiel soll diese 15 Zeichen betragen:

print(f"π ≈ {math.pi:15.3f}")
π ≈           3.142

Alle Möglichkeiten, die Format Specifier innerhalb der geschwungenen Klammern bieten, finden sich in der offiziellen Dokumentation (inklusive einer Menge Beispiele).

Dateien lesen und schreiben

Die Funktion open gibt ein Datei-Objekt zurück, welches zum Lesen oder Schreiben von Dateien verwendet werden kann (die im Beispiel verwendete Datei test.txt ist hier verfügbar):

f = open("test.txt", "w")

Das erste Argument ist der Dateiname, und das zweite Argument beschreibt den Modus ("r" lesen, "w" schreiben, "a" hinzufügen). Standardmäßig wird der Modus "r" angenommen, wenn das Argument nicht übergeben wird.

Mit dem Datei-Objekt f kann man dann von der Datei lesen bzw. in die Datei schreiben.

Wichtig

Nach Beendigung aller Lese- und Schreiboperationen muss man das Datei-Objekt wieder schließen:

f.close()

Es ist sinnvoll, alle Datei-Operationen in einem with-Block durchzuführen; so wird die Datei bei Verlassen des Blocks automatisch geschlossen:

with open("test.txt") as f:
    data = f.read()

Zu Beachten ist noch, dass der Dateiname als String übergeben wird. Dieser enthält entweder den vollständigen Pfad der zu öffnenden Datei (z.B. "C:/Program Files/Test Program/test.txt"), oder nur den Dateinamen. Im letzteren Fall wird dann angenommen, dass sich die Datei im Arbeitsverzeichnis befindet.

Hinweis

Pfade sollten immer mit einem normalen / getrennt werden und nicht wie unter Windows üblich mit einem verkehrten \.

Die Methode read eines Datei-Objektes liest die gesamte Datei ein und gibt den Inhalt als String zurück:

f = open("test.txt")
text = f.read()
print(text)
Hello!

This is just a test file containing some random text.

Nice!

Der Inhalt einer Datei kann immer nur ein Mal gelesen werden. Wenn bereits der gesamte Inhalt gelesen wurde, befindet sich der sogenannte Dateizeiger am Ende der Datei. Wenn dann nochmals gelesen wird, wird nur mehr ein leerer String zurückgegeben:

f.read()  # Ende der Datei bereits erreicht
''

Möchte man die Datei erneut lesen, muss man sie schließen und kann sie dann wieder erneut öffnen:

f.close()  # schließt Datei (notwendig wenn man kein with verwendet)

Die Methode readline liest eine Zeile aus der Datei:

with open("test.txt") as f:
    print("1. Zeile: ", f.readline(), end="")
    print("2. Zeile: ", f.readline(), end="")
    print("3. Zeile: ", f.readline(), end="")
1. Zeile:  Hello!
2. Zeile:  
3. Zeile:  This is just a test file containing some random text.

In einer Schleife kann man die Datei Zeile für Zeile auslesen, in dem man über das Datei-Objekt iteriert:

with open("test.txt") as f:
    for line in f:
        print(line, end="")
Hello!

This is just a test file containing some random text.

Nice!

So kann man einzelne Zeilen einfach manipulieren, z.B. um Zeilennummern auszugeben (die nicht in der Datei selbst sind):

with open("test.txt") as f:
    for no, line in enumerate(f):
        print(no, line, end="")
0 Hello!
1 
2 This is just a test file containing some random text.
3 
4 Nice!
Tipp

In der for-Schleife wird hier nicht direkt über f, sondern über enumerate(f) iteriert. Diese Funktion zählt die Schleifendurchläufe mit und gibt ein Tupel bestehend aus dem aktuellen Wert des Schleifenzählers sowie der aktuellen Zeile zurück.

Text in Dateien schreiben funktioniert sehr ähnlich – man öffnet die Datei im "w"-Modus und übergibt der write-Methode den gewünschten Inhalt als String:

with open("test2.txt", "w") as f:
    f.write("Das ist ein Test.\nSo kann man einfach\nText\nin Dateien schreiben.")

In diesem Beispiel steht die Escape-Sequenz \n für einen Zeilenumbruch (neue Zeile).

Beispiel: CSV-Datei lesen

Mit den bis jetzt vorgestellten Mitteln können wir bereits CSV1-Dateien einlesen und rudimentäre Statistiken rechnen. CSV-Dateien werden häufig verwendet, um tabellarische Daten abzuspeichern. Diese Dateien sind reine Textdateien, in denen die Variablen (Spalten) durch Kommas getrennt sind und die einzelnen Datensätze zeilenweise vorliegen.

Im Folgenden werden drei Möglichkeiten zum Lesen einer CSV-Datei vorgestellt (die Beispieldatei correlation.csv ist hier verfügbar). Die erste Methode verwendet Python ohne spezielle Module, die zweite Methode ist eine Verallgemeinerung der ersten. Die dritte und einfachste Variante verwendet das Package pandas, welches wir in dieser Lehrveranstaltung nicht genauer kennenlernen werden (sie wird der Vollständigkeit halber aber trotzdem erwähnt).

Möglichkeit 1

Wir starten mit einer leeren Liste data. Dieser Liste fügen wir den Inhalt jeder Zeile schrittweise (mittels append) hinzu. Eine Zeile wird dabei als Liste mit zwei Strings abgebildet.

data = []  # leere Liste
with open("correlation.csv") as f:
    for row in f:
        data.append(row.strip().split(","))
data
[['essay', 'hours'],
 ['61.6754974920745', '10.6303372921189'],
 ['69.5450056362295', '7.28522617959232'],
 ['48.2293039713449', '5.05204775449098'],
 ['70.6786516311817', '2.88661436120476'],
 ['59.8996225857332', '9.54501229482367'],
 ['61.1620237718455', '11.3108384701452'],
 ['67.6247016928604', '7.46507180351913'],
 ['64.7790589873963', '8.47127906794298'],
 ['63.207063456166', '8.71537345316006'],
 ['49.6910817003072', '6.20347356947365'],
 ['63.7189196026189', '4.77251550782322'],
 ['72.2425933666743', '10.9815424018851'],
 ['70.5862210579123', '5.2215409766568'],
 ['58.6398615751171', '6.85848450383968'],
 ['58.7153800614887', '9.7984118965962'],
 ['67.3999698058767', '9.49072930246925'],
 ['60.7037631406063', '6.51736420324962'],
 ['60.931351175695', '11.8021326615165'],
 ['62.3401801239333', '8.96313112993931'],
 ['59.9662288209405', '5.42249029575092'],
 ['52.1619843495503', '6.40972025602273'],
 ['76.5879640890472', '15.0104773734972'],
 ['60.9196973933278', '8.07064211676739'],
 ['66.6330159660378', '10.9720986214915'],
 ['73.6195367228337', '14.7776461317624'],
 ['60.2224731091917', '5.2455561967939'],
 ['59.257246898708', '8.67216132385057'],
 ['51.9302907572568', '8.55546259639567'],
 ['59.4301075638833', '6.08144370899007'],
 ['69.4087175381595', '7.42190548469444'],
 ['65.2986136310666', '12.3137937416164'],
 ['60.2838297249765', '9.93443882388444'],
 ['60.8535237978012', '7.79529820172183'],
 ['71.6333469504558', '6.5627956116459'],
 ['69.1407939072155', '10.2828636303044'],
 ['66.4753904156445', '5.11639249646656'],
 ['63.0477561140632', '8.66268357519631'],
 ['68.1678280737943', '6.59320568183171'],
 ['64.7480342462125', '3.70537792316693'],
 ['68.2804343717536', '12.9178683539958'],
 ['60.4052387002632', '8.6962361085862'],
 ['64.6313248038771', '11.2150596156186'],
 ['58.2594898681359', '6.83244891007452'],
 ['52.2352473953415', '8.62308388223269'],
 ['79.8777504975499', '7.84368345377415']]

Nun können die Daten in eine Struktur umgewandelt werden, die sich besser für nachfolgende Berechnungen eignet (also inbesondere werden die Zahlen, die als Strings vorliegen, mit der Funktion float in Kommazahlen umgewandelt).

cols = [[], []]  # two empty lists for the two columns
for row in data[1:]:  # skip first row (header)
    cols[0].append(float(row[0]))  # first column
    cols[1].append(float(row[1]))  # second column

Nun stehen die beiden Spalten in der Liste cols zur weiteren Verfügung. Diese Liste hat zwei Elemente:

len(cols)
2

Die beiden Einträge in cols sind ebenfalls Listen:

type(cols[0]), type(cols[1])
(list, list)

Die Länge beider Listen entspricht der Anzahl an Zeilen in der CSV-Datei (ohne Kopfzeile):

len(cols[0]), len(cols[1])
(45, 45)

Die Elemente in diesen beiden Listen sind Zahlen:

cols[0]
[61.6754974920745,
 69.5450056362295,
 48.2293039713449,
 70.6786516311817,
 59.8996225857332,
 61.1620237718455,
 67.6247016928604,
 64.7790589873963,
 63.207063456166,
 49.6910817003072,
 63.7189196026189,
 72.2425933666743,
 70.5862210579123,
 58.6398615751171,
 58.7153800614887,
 67.3999698058767,
 60.7037631406063,
 60.931351175695,
 62.3401801239333,
 59.9662288209405,
 52.1619843495503,
 76.5879640890472,
 60.9196973933278,
 66.6330159660378,
 73.6195367228337,
 60.2224731091917,
 59.257246898708,
 51.9302907572568,
 59.4301075638833,
 69.4087175381595,
 65.2986136310666,
 60.2838297249765,
 60.8535237978012,
 71.6333469504558,
 69.1407939072155,
 66.4753904156445,
 63.0477561140632,
 68.1678280737943,
 64.7480342462125,
 68.2804343717536,
 60.4052387002632,
 64.6313248038771,
 58.2594898681359,
 52.2352473953415,
 79.8777504975499]
cols[1]
[10.6303372921189,
 7.28522617959232,
 5.05204775449098,
 2.88661436120476,
 9.54501229482367,
 11.3108384701452,
 7.46507180351913,
 8.47127906794298,
 8.71537345316006,
 6.20347356947365,
 4.77251550782322,
 10.9815424018851,
 5.2215409766568,
 6.85848450383968,
 9.7984118965962,
 9.49072930246925,
 6.51736420324962,
 11.8021326615165,
 8.96313112993931,
 5.42249029575092,
 6.40972025602273,
 15.0104773734972,
 8.07064211676739,
 10.9720986214915,
 14.7776461317624,
 5.2455561967939,
 8.67216132385057,
 8.55546259639567,
 6.08144370899007,
 7.42190548469444,
 12.3137937416164,
 9.93443882388444,
 7.79529820172183,
 6.5627956116459,
 10.2828636303044,
 5.11639249646656,
 8.66268357519631,
 6.59320568183171,
 3.70537792316693,
 12.9178683539958,
 8.6962361085862,
 11.2150596156186,
 6.83244891007452,
 8.62308388223269,
 7.84368345377415]

Mit diesen beiden Listen kann man jetzt also Berechnungen durchführen. Beispielsweise kann man die Spaltenmittelwerte so ausrechnen:

sum(cols[0]) / len(cols[0])
63.44991370093669
sum(cols[1]) / len(cols[1])
8.349021354368455

Diese Lösung ist allerdings alles andere als ideal. Erstens sollte man immer bereits existierende Lösungen verwenden und nicht alles von Grund auf neu entwickeln (es gibt in Python wie bereits erwähnt ein eigenes csv-Modul, und das Package pandas ist für solche Aufgaben geradezu prädestiniert). Zweitens ist diese Lösung auch sehr auf die konkrete Datei angepasst, z.B. funktioniert der Ansatz nur für eine Datei mit exakt zwei Spalten.

Möglichkeit 2

Eine verallgemeinerte Lösung für eine beliebige Anzahl an Spalten sieht wie folgt aus:

with open("correlation.csv") as f:
    header = f.readline().strip().split(",")  # erste Zeile

    data = {}  # dict mit einem Eintrag pro Spalte
    for name in header:
        data[name] = []  # Key ist der Spaltenname, Value eine leere Liste

    for row in f:  # restliche Zeilen
        values = row.strip().split(",")
        for h, v in zip(header, values):
            data[h].append(float(v))  # füge zur jeweiligen Liste hinzu

Hier werden die Variablennamen aus der ersten Zeile (der Kopfzeile) gelesen. Die Daten werden dann in ein Dictionary geschrieben, wobei die Werte der Spalten zu den richtigen Dictionary-Einträgen (Listen) hinzugefügt werden. Die Funktion zip kombiniert die Werte von header und values in ein Tupel, die dann innerhalb der Schleife als h bzw. v zur Verfügung stehen.

data
{'essay': [61.6754974920745,
  69.5450056362295,
  48.2293039713449,
  70.6786516311817,
  59.8996225857332,
  61.1620237718455,
  67.6247016928604,
  64.7790589873963,
  63.207063456166,
  49.6910817003072,
  63.7189196026189,
  72.2425933666743,
  70.5862210579123,
  58.6398615751171,
  58.7153800614887,
  67.3999698058767,
  60.7037631406063,
  60.931351175695,
  62.3401801239333,
  59.9662288209405,
  52.1619843495503,
  76.5879640890472,
  60.9196973933278,
  66.6330159660378,
  73.6195367228337,
  60.2224731091917,
  59.257246898708,
  51.9302907572568,
  59.4301075638833,
  69.4087175381595,
  65.2986136310666,
  60.2838297249765,
  60.8535237978012,
  71.6333469504558,
  69.1407939072155,
  66.4753904156445,
  63.0477561140632,
  68.1678280737943,
  64.7480342462125,
  68.2804343717536,
  60.4052387002632,
  64.6313248038771,
  58.2594898681359,
  52.2352473953415,
  79.8777504975499],
 'hours': [10.6303372921189,
  7.28522617959232,
  5.05204775449098,
  2.88661436120476,
  9.54501229482367,
  11.3108384701452,
  7.46507180351913,
  8.47127906794298,
  8.71537345316006,
  6.20347356947365,
  4.77251550782322,
  10.9815424018851,
  5.2215409766568,
  6.85848450383968,
  9.7984118965962,
  9.49072930246925,
  6.51736420324962,
  11.8021326615165,
  8.96313112993931,
  5.42249029575092,
  6.40972025602273,
  15.0104773734972,
  8.07064211676739,
  10.9720986214915,
  14.7776461317624,
  5.2455561967939,
  8.67216132385057,
  8.55546259639567,
  6.08144370899007,
  7.42190548469444,
  12.3137937416164,
  9.93443882388444,
  7.79529820172183,
  6.5627956116459,
  10.2828636303044,
  5.11639249646656,
  8.66268357519631,
  6.59320568183171,
  3.70537792316693,
  12.9178683539958,
  8.6962361085862,
  11.2150596156186,
  6.83244891007452,
  8.62308388223269,
  7.84368345377415]}

Diese Lösung funktioniert also bereits mit einer beliebigen Spaltenanzahl, setzt aber voraus, dass es eine Kopfzeile gibt. Die Spaltenmittelwerte können dann wie folgt berechnet werden:

sum(data["essay"]) / len(data["essay"])
63.44991370093669
sum(data["hours"]) / len(data["hours"])
8.349021354368455

Möglichkeit 3

Das Package pandas eignet sich ideal zum Verarbeiten von tabellarischen Daten. Es beinhaltet auch Funktionen zum Lesen von Dateien. Damit würde der Code wie folgt aussehen:

import pandas as pd

data = pd.read_csv("correlation.csv")
data
essay hours
0 61.675497 10.630337
1 69.545006 7.285226
2 48.229304 5.052048
3 70.678652 2.886614
4 59.899623 9.545012
5 61.162024 11.310838
6 67.624702 7.465072
7 64.779059 8.471279
8 63.207063 8.715373
9 49.691082 6.203474
10 63.718920 4.772516
11 72.242593 10.981542
12 70.586221 5.221541
13 58.639862 6.858485
14 58.715380 9.798412
15 67.399970 9.490729
16 60.703763 6.517364
17 60.931351 11.802133
18 62.340180 8.963131
19 59.966229 5.422490
20 52.161984 6.409720
21 76.587964 15.010477
22 60.919697 8.070642
23 66.633016 10.972099
24 73.619537 14.777646
25 60.222473 5.245556
26 59.257247 8.672161
27 51.930291 8.555463
28 59.430108 6.081444
29 69.408718 7.421905
30 65.298614 12.313794
31 60.283830 9.934439
32 60.853524 7.795298
33 71.633347 6.562796
34 69.140794 10.282864
35 66.475390 5.116392
36 63.047756 8.662684
37 68.167828 6.593206
38 64.748034 3.705378
39 68.280434 12.917868
40 60.405239 8.696236
41 64.631325 11.215060
42 58.259490 6.832449
43 52.235247 8.623084
44 79.877750 7.843683

Die Spaltenmittelwerte kann man dann wie folgt berechnen:

data.mean()
essay    63.449914
hours     8.349021
dtype: float64

Übungen

Übung 1

Erstellen Sie eine Liste lst, welche die geraden Zahlen zwischen 0 und 100 (inklusive) beinhaltet, und speichern Sie diese Zahlen in eine Datei ue1.txt. In der Datei soll jede Zahl in einer eigenen Zeile stehen.

Hinweis

Zahlen sollten vor dem Schreiben in die Datei mit der Funktion str in Strings umgewandelt werden. Das Zeichen für einen Zeilenumbruch lautet \n. Sie können z.B. eine for-Schleife über die Zahlenliste machen und die einzelnen Elemente nacheinander in die Datei schreiben. Alternativ können Sie auch die Liste in einen richtig formatierten String umwandeln (die String-Methode join ist hier hilfreich) und diesen dann in die Datei schreiben.

Übung 2

Verwenden Sie wieder die Liste lst aus Übung 1, aber schreiben Sie die Zahlen nun so in eine Datei ue2.txt, dass diese durch Kommas getrennt in einer Zeile stehen.

Übung 3

Das Spiel Scrabble eignet sich hervorragend, um über die Existenz von Wörtern zu diskutieren. Zumindest für die englische Ausgabe gibt es eine offizielle Wortliste, die man konsultieren kann, wenn man nicht sicher ist, ob es sich bei einer Kreation um ein gültiges Wort handelt oder nicht. Alle Wörter in dieser Datenbank können als Textdatei heruntergeladen werden.

Für diese Übung laden Sie sich diese Datei herunter und speichern Sie sie in Ihrem Arbeitsverzeichnis ab. Lesen Sie die Datei dann in Python ein und finden Sie heraus, wie viele Wörter (Zeilen) sich in der Datei befinden.

Hinweis

Iterieren Sie mit einer for-Schleife über das Datei-Objekt und zählen Sie die Schleifendurchläufe mit. Am Ende der Schleife (d.h. am Ende der Datei) entspricht dann ihr Zähler der Zeilenanzahl der Datei (d.h. der Anzahl der Wörter). Beachten Sie, dass die ersten paar Zeilen in der Datei keine Wörter beinhalten!

Wenn man es ganz exakt machen möchte, sollte man auch noch überprüfen, ob die aktuelle Zeile auch tatsächlich ein Wort enthält (also keine Leerzeile ist).

Übung 4

Verwenden Sie die Wörter-Datei aus Übung 3 und finden Sie heraus, wie viele Wörter mit mehr als 14 Buchstaben in der Liste enthalten sind (also Wörter mit 15 Buchstaben oder mehr).

Hinweis

Wenn Sie die Datei Zeile für Zeile einlesen, entfernen Sie unsichtbare Zeichen (wie z.B. Zeilenumbruch) mit der Methode strip, bevor Sie die Länge des aktuellen Wortes bestimmen (ansonsten ist die Länge immer um 1 größer als die Länge des Wortes, weil der Inhalt einer Zeile z.B. abandon\n lautet, und \n wird als ein Zeichen gezählt). Erhöhen Sie Ihren Zähler nur, wenn die Länge des aktuellen Wortes (ohne Zeilenumbruch) größer als 14 ist.

Fußnoten

  1. Comma-Separated Values↩︎