Ein- und Ausgabe¶
Das Ergebnis der Abarbeitung eines Programms wird in den meisten Fällen die
Ausgabe des Ergebnisses zur Folge haben. In den bisherigen Kapiteln haben wir
die Ausgabe auf dem Bildschirm mit Hilfe der print
-Funktion kennengelernt.
Insbesondere bei umfangreicheren Ausgaben wird man das Ergebnis in eine Datei
schreiben wollen, um es zu speichern oder später weiter zu verarbeiten,
beispielsweise mittels eines Grafikprogramms. Häufig ist es auch notwendig, dem
Programm Parameter zu übergeben, entweder beim Programmaufruf über Argumente
auf der Kommandozeile, auf Anfrage des Programms über die Tastatur oder durch
Einlesen aus einer Datei. Im Folgenden werden wir uns zunächst die Eingabe über
die Kommandozeile und die Tastatur ansehen und uns anschließend mit der Ein-
und Ausgabe von Daten mit Hilfe von Dateien beschäftigen.
Eingabe über die Kommandozeile und die Tastatur¶
Ein Python-Programm, nennen wir es foo.py
[1], lässt sich von der Kommandozeile
mit Hilfe des Aufrufs
python foo.py
starten. An diesen Aufruf kann man jedoch auch weitere Argumente anhängen, die innerhalb des Programms verfügbar sind. Ein solcher Aufruf könnte beispielsweise lauten
python foo.py Hallo 3
Es könnte sich dabei um den Aufruf eines Programms handeln, das den angegebenen
String so oft ausgibt wie es durch das letzte Argument des Aufrufs vorgegeben
ist. Der Zugriff auf die Argumente erfolgt dabei über die argv
-Variable
des sys
-Moduls, wobei argv
für »argument vector« steht. Eine
Realisierung des Programms könnte also folgendermaßen aussehen:
1 2 3 4 5 | import sys
print(sys.argv)
for n in range(int(sys.argv[2])):
print(sys.argv[1])
|
Dieser Code ergibt die folgende Ausgabe
['foo.py', 'Hallo', '3']
Hallo
Hallo
Hallo
Aus der ersten Zeile der Ausgabe wird deutlich, dass die Variable sys.argv
die Kommandozeilenargumente in Form einer Liste enthält. Dabei gibt das erste
Element den Namen des aufgerufenen Programms an. Dies ist unter anderem dann
von Interesse, wenn man das Programm unter verschiedenen Namen aufrufen kann
und dabei ein unterschiedliches Verhalten erreichen möchte. Des Weiteren zeigt
die erste Zeile der Ausgabe, dass alle Argumente in der Liste sys.argv
als
Strings auftreten. Daher musste das letzte Argument des Programmaufrufs in
Zeile 4 des Programmcodes erst mit Hilfe der int()
-Funktion in einen
Integer umgewandelt werden.
Will man für ein Programm eine flexible oder umfangreiche Übergabe von Optionen
auf der Kommandozeile vorsehen, so lohnt sich ein Blick auf das
argparse
-Modul [2]. Dieses stellt einige nützliche
Funktionalitäten zur Verfügung, unter anderem auch die automatisierte
Erstellung einer Hilfeausgabe.
Eingaben können auch während des Programmablaufs erfolgen. Dies geschieht mit
Hilfe der input()
-Funktion, die in dem folgenden Beispiel illustriert
wird.
1 2 3 4 5 6 | while 1:
try:
x = input("Aufgabe: ")
print(eval(x))
except SyntaxError:
break
|
Uns kommt es zunächst auf die Zeile 3 an. Die input()
-Funktion gibt das
Stringargument aus und gibt die Eingabe als String, hier an die Variable x
,
zurück. Diese Funktionalität wird in dem Beispiel verwendet, um vom Benutzer
eingegebene Rechenaufgaben zu lösen. Hierzu wird mit der Zeile 1 eine
Dauerschleife eingeleitet, in der in Zeile 3 versucht wird, eine Aufgabe
einzulesen. In Zeile 4 wird die eval()
-Funktion verwendet, um den String
als Anweisung zu interpretieren und auszuführen. Tritt dabei ein
SyntaxError
auf, so wird die Dauerschleife in den Zeilen 5 und 6 beendet.
Die Eingabe kann aber auch einfach in einem Tupel bestehen, dessen Bestandteile an mehrere Variablen übergeben werden:
x = y = 1
while x*y:
x, y = eval(input("Multiplikanden: "))
print("Produkt = {}".format(x*y))
Dabei muss die Eingabe das erforderliche Komma enthalten:
Multiplikanden: 4, 5
Produkt = 20
Multiplikanden: 3, 0
Produkt = 0
In diesem Beispiel wird die Schleife beendet, sobald das Produkt gleich Null ist, was
dem Wahrheitswert False
entspricht. In einem anderen Beispiel wird eine Liste
eingegeben, die in einer Schleife abgearbeitet wird.
zahlen = eval(input("Geben Sie eine Liste ein: "))
for n in zahlen:
print("{:6}\t{:10}".format(n, n**2))
Die Eingabe einer Liste gibt die Listenelemente und die zugehörigen Quadrate aus:
Geben Sie eine Liste ein: [-17, 5, 247]
-17 289
5 25
247 61009
Lesen und Schreiben von Dateien¶
Häufig wird man statt der manuellen Eingabe von Daten und der Ausgabe von
Ergebnissen auf dem Bildschirm das Einlesen aus einer Datei und das Schreiben
in eine Datei vorziehen. Wir betrachten zunächst den Fall, dass eine Datei
vorliegt, auf deren Inhalt wir in einem Programm zugreifen wollen. Für die
folgenden Beispiele nehmen wir an, dass eine Datei namens foo_utf8.dat
mit
dem Inhalt
Einführung in das
Programmieren für
Physiker und
Materialwissenschaftler
existiert. Dabei liege diese Datei in der UTF-8-Kodierung vor. Zur Illustration
sei noch eine Datei foo_latin1.dat
vorhanden, die die ISO-8859-1-Kodierung,
auch als Latin-1-Kodierung bekannt, verwendet. Während in der ersten Datei der
Umlaut »ü« hexadezimal durch C3BC
kodiert ist, ist er in der zweiten Datei
hexadezimal als FC
dargestellt.
Bevor Daten aus dieser Datei gelesen werden können, muss die Datei geöffnet werden. Dies könnte wie in der ersten Zeile gezeigt geschehen:
1 2 3 | >>> datei = open("foo_utf8.dat")
>>> datei
<_io.TextIOWrapper name='foo.dat' encoding='UTF-8'>
|
Damit haben wir ein Dateiobjekt erhalten, das den Zugriff auf die Datei mit dem in der ersten Zeile als Argument angegebenen Namen ermöglicht. Falls nichts anderes beim Öffnen der Datei angegeben wird, ist die Datei lediglich zum Lesen geöffnet. Die Datei kann also nicht überschrieben werden, und es kann auch nichts angefügt werden.
Standardmäßig erwartet wird die auf dem jeweiligen System bevorzugte Kodierung. In unserem Fall ist dies UTF-8.
>>> import locale
>>> locale.getpreferredencoding()
'UTF-8'
Der Versuch, auf eine nicht existierende Datei lesend zuzugreifen, wird mit
einem IOError
beantwortet:
>>> datei = open("foo.txt")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'foo.txt'
Nachdem die Datei geöffnet wurde, gibt es verschiedene Möglichkeiten, auf ihren Inhalt
zuzugreifen. Mit der read()
-Funktion wird, sofern kein Argument eingegeben wurde,
die gesamte Datei in einen String eingelesen:
>>> datei.read()
'Einführung in das\nProgrammieren für\nPhysiker und\nMaterialwissenschaftler\n'
Die in dem String auftretenden \n
geben Zeilenumbrüche an [3]. Die
Datei besteht also aus vier Zeilen. Versucht man auf die gleiche Weise, die
Datei foo_latin1.dat
einzulesen, erhält man einen UnicodeDecodeError
weil die den Umlauten entsprechenden Bytes in dieser Datei nicht im Rahmen der
UTF-8-Kodierung interpretiert werden können.
>>> datei = open("foo_latin1.dat")
>>> datei.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/codecs.py", line 321, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xfc in position 4: invalid start byte
Dagegen funktioniert das Einlesen problemlos, wenn man die richtige Kodierung angibt.
>>> datei = open("foo_latin1.dat", encoding="latin1")
>>> datei.read()
'Einführung in das\nProgrammieren für\nPhysiker und\nMaterialwissenschaftler\n'
Nach dem Lesen einer gesamten Datei mit der read()
-Funktion steht der
Zeiger, der die aktuelle Position in der Datei angibt, am Dateiende. Ein
weiteres Lesen ergibt daher nur einen leeren String wie Zeile 7 in dem
folgenden Beispiel zeigt.
1 2 3 4 5 6 7 | >>> datei = open("foo_utf8.dat")
>>> datei.read()
'Einführung in das\nProgrammieren für\nPhysiker und\nMaterialwissenschaftler\n'
>>> datei.tell()
75
>>> datei.read()
''
|
In den Zeilen 4 und 5 haben wir mit Hilfe der tell()
-Methode die
aktuelle Position des Dateizeigers abgefragt. Dabei zählen die Zeilenumbrüche
mit. Man muss allerdings beachten, dass das Resultat der tell()
-Methode
auf der Bytedarstellung beruht und das UTF8-kodierte ü
als zwei Bytes zählt.
Mit Hilfe der seek()
-Funktion kann man gezielt an bestimmte Stellen der
Datei springen, wobei allerdings wieder die Bytedarstellung relevant ist. Es
besteht also potentiell die Gefahr, mitten in einem Mehrbyte-Code zu
landen. Daher ist es sinnvoll, seek()
auf der Basis von Positionen zu
verwenden, die mit tell()
bestimmt wurden.
Eindeutig ist jedoch der Dateianfang, der der Zeigerposition 0
entspricht.
Nach einem seek(0)
liest der zweite Aufruf der read()
-Funktion im
folgenden Beispiel nochmals die gesamte Datei ein:
1 2 3 4 5 6 7 8 | >>> datei = open("foo_utf8.dat")
>>> datei.read()
'Einführung in das\nProgrammieren für\nPhysiker und\nMaterialwissenschaftler\n'
>>> datei.seek(0)
>>> datei.read(10)
'Einführung'
>>> datei.read(10)
' in das\nPr'
|
Man kann sich die Funktionsweise wie bei einem Magnetband vorstellen, bei dem
die Position des Lesekopfes durch die tell()
-Funktion angegeben wird,
während die seek()
-Funktion den Lesekopf neu positioniert. Im gerade
gezeigten Beispiel wird der Lesekopf an den Anfang, d.h. auf die absolute
Position 0
zurückgesetzt. Anschließend werden zweimal je zehn Zeichen
eingelesen.
Nicht immer möchte man die ganze Datei auf einmal einlesen, sei es weil die
Datei sehr groß ist oder weil man den Inhalt zum Beispiel zeilenweise
verarbeiten möchte. Hierzu stellt Python verschiedene Möglichkeiten zur
Verfügung. Mit Hilfe der readlines()
-Funktion lassen sich die einzelnen
Zeile für die weitere Verarbeitung in eine Liste aufnehmen:
>>> datei = open("foo_utf8.dat")
>>> inhalt = datei.readlines()
>>> print(inhalt)
['Einführung in das\n', 'Programmieren für\n', 'Physiker und\n',
'Materialwissenschaftler\n']
Bei der Verarbeitung der einzelnen Zeilen ist zu beachten, dass die
Zeichenketten am Ende noch die Zeilenumbruchkennzeichnung \n
enthalten.
Zeilen lassen sich auch einzeln einlesen.
>>> datei = open("foo_utf8.dat")
>>> datei.readline()
'Einführung in das\n'
>>> datei.readline()
'Programmieren für\n'
>>> datei.readline()
'Physiker und\n'
>>> datei.readline()
'Materialwissenschaftler\n'
>>> datei.readline()
''
Nachdem alle Zeilen eingelesen wurden, steht der Dateizeiger am Dateiende, so
dass bei weiteren Aufrufen der readline()
-Funktion nur ein leerer String
zurückgegeben wird.
Eine elegante Methode, die Zeilen einer Datei in einer Schleife abzuarbeiten, zeigt das folgende Beispiel.
>>> datei = open("foo_utf8.dat")
>>> for zeile in datei:
... print(zeile.upper())
...
EINFÜHRUNG IN DAS
PROGRAMMIEREN FÜR
PHYSIKER UND
MATERIALWISSENSCHAFTLER
Will man die zusätzlichen Leerzeilen vermeiden, so muss man das \n
am Ende
der Zeilen entfernen, entweder unter Verwendung der rstrip()
-Methode oder
durch Verwendung eines Slices.
>>> datei = open("foo_utf8.dat")
>>> for zeile in datei:
... print(zeile.upper().rstrip("\n"))
...
EINFÜHRUNG IN DAS
PROGRAMMIEREN FÜR
PHYSIKER UND
MATERIALWISSENSCHAFTLER
Die gleiche Ausgabe erhält man mit
>>> datei = open("foo_utf8.dat")
>>> for zeile in datei:
... print(zeile[:-1].upper())
...
In allen bisherigen Beispielen haben wir eine Anweisung unterschlagen, die man am Ende der Arbeit mit einer Datei immer ausführen lassen sollte. Nachdem man eine Datei zunächst geöffnet hat, sollte man sie am Ende auch wieder schließen.
>>> datei = open("foo_utf8.dat")
>>> inhalt = datei.read()
>>> datei.closed
False
>>> datei.close()
>>> datei.closed
True
Das Schließen einer Datei gibt die im Zusammenhang mit der geöffneten Datei benötigten Ressourcen wieder frei und bewahrt einen unter Umständen auch vor einem teilweisen oder vollständigen Verlust der geschriebenen Daten.
Bevor wir uns mit dem Schreiben von Dateien beschäftigen, müssen wir uns
zunächst noch ansehen, wie man Zahlen aus einer Datei liest, eine bei
numerischen Arbeiten sehr häufige Situation. Als Eingabedatei sei eine Datei
namens spam.dat
[4] mit dem Inhalt
1.37 2.59
10.3 -1.3
5.8 2.0
gegeben. Das folgende Programm berechnet zeilenweise das Produkt des jeweiligen Zahlenpaares.
1 2 3 4 5 | daten = open("spam.dat")
for zeile in daten:
x, y = zeile.split()
print(float(x)*float(y))
daten.close()
|
In Zeile 3 wird jede eingelesene Zeile an Leerräumen wie zum Beispiel
Leerstellen oder Tabulatorzeichen, aufgeteilt. Damit ergeben sich je zwei
Strings, die die Information über die jeweilige Zahl enthalten. Allerdings kann
man Strings nicht miteinander multiplizieren. Daher muss in Zeile 4 vor der
Multiplikation mit Hilfe der float()
-Funktion eine Umwandlung in
Gleitkommazahlen erfolgen. Das Schließen der Datei erfolgt in Zeile 5 außerhalb
der for
-Schleife, da sonst die Datei bereits nach dem Einlesen der ersten
Zeile geschlossen würde.
Als Alternative zu der im vorigen Beispiel gezeigten expliziten Umwandlung kann
es sinnvoll sein, die von Python zur Verfügung gestellte map()
-Funktion
zu verwenden. Dies ist insbesondere bei mehreren Zahlen oder wenn deren Anzahl
nicht bekannt ist, nützlich. Das Beispiel lautet dann
1 2 3 4 5 | daten = open("spam.dat")
for zeile in daten:
x, y = map(float, zeile.split())
print(x*y)
daten.close()
|
Dabei wird in Zeile 3 die float()
-Funktion zur Umwandlung aller Elemente
der Liste zeile.split()
in Gleitkommazahlen angewandt.
In einem Programm möchte man nicht nur Daten aus einer Datei einlesen, sondern vor allem auch die Ergebnisse in einer Datei speichern. Wie beim Lesen aus Dateien muss man beim Schreiben in Dateien zunächst eine Datei öffnen. Dies kann auf verschiedene Weise geschehen. Betrachten wir zunächst das folgende Beispiel:
1 2 3 4 | datei = open("foo.dat", "w")
for n in range(5):
datei.write("{:4}{:4}\n".format(n, n*n))
datei.close()
|
Die Anweisung in Zeile 1 kennen wir im Prinzip schon, nur dass jetzt das zweite
Argument explizit auf w
, also »write« gesetzt ist. Damit wird die Datei
foo.dat
zum Schreiben geöffnet. Ob die Datei schon existiert, ist dabei
unerheblich. Existiert sie nicht, so wird eine neue Datei angelegt. Existiert
die Datei dagegen schon, so wird ihre Länge vor dem Schreiben auf Null gesetzt.
Damit wird die zuvor existierende Datei effektiv überschrieben. In der dritten
Zeile erfolgt das Schreiben in die Datei mit Hilfe der write()
-Methode.
Wie bei dem uns bereits bekannten print
-Befehl muss als Argument ein String
angegeben werden. Dabei können natürlich die im Abschnitt Formatierung von Ausgaben
besprochenen Formatspezifikationen verwendet werden. Zu beachten ist, dass im
Gegensatz zur print
-Anweisung bei Bedarf ein Zeilenumbruch explizit mit
\n
zu verlangen ist. Die read()
- und die write()
-Methode sind
also insofern symmetrisch als in beiden Fällen der Zeilenumbruch in den
jeweiligen Zeichenketten explizit auftritt. Nicht vergessen werden sollte das
Schließen der Datei in Zeile 4, da ansonsten die Gefahr bestehen könnte, dass
Daten verloren gehen.
Öffnet man eine existierende Datei im Modus r+
, so kann man von ihr lesen
und in sie schreiben. Ähnliches geschieht bei w+
, wobei bei Bedarf jedoch
eine neue Datei angelegt wird. Gelegentlich möchte man Daten an eine Datei
anhängen. In diesem Falle verwendet man den Modus a
für »append« oder
a+
falls man aus der Datei auch lesen möchte.
Ab Python 3.3 gibt es auch noch die Option "x"
, die nur
dann eine Datei erfolgreich öffnet, falls diese Datei noch nicht existiert.
Bei numerischen Rechnungen ist es oft sinnvoll, die verwendeten Parameter im Dateinamen aufzuführen wie es im folgenden Beispiel gezeigt ist. Dazu wird in der Zeile 2 beim Öffnen der Datei ein geeigneter Konvertierungsspezifikator verwendet.
1 2 3 4 5 | parameter = 12
datei = open("resultate_{:05}.dat".format(parameter), "w")
for n in range(parameter):
datei.write("{:10}\n".format(n*n))
datei.close()
|
Entsprechend dem Wert der Variable parameter
erfolgt die Ausgabe in die
Datei resultate_00012.dat
. Die Formatiervorgabe, den Integer bis zur
geforderten Feldbreite von links mit Nullen aufzufüllen, ist hier nützlich, um
bei einer großen Anzahl von Parameterwerten eine ordentlich sortierte Übersicht
über die vorhandenen Dateien bekommen zu können.
Da das Überschreiben von Dateien unangenehme Folgen haben kann, ist es nützlich
zu wissen, wie man die Existenz einer Datei überprüfen kann. Mit einer Methode
aus dem os.path
-Modul geht das wie im Folgenden gezeigt,
import os
datei = "foo.dat"
if os.path.exists(datei):
print("Achtung! {} existiert bereits.".format(datei))
else:
print("Die Datei {} existiert noch nicht.".format(datei))
Existiert die Datei bereits, so würde man in einer echten Anwendung dem Benutzer wohl die Möglichkeit geben, das Programm an dieser Stelle geordnet zu beenden oder einen alternativen Dateinamen anzugeben.
Abschließend sei noch erwähnt, dass Python für bestimmte Dateiformate spezielle
Module zum Lesen und Schreiben zur Verfügung stellt. Hierzu gehört zum Beispiel
das csv
-Modul, das den Zugriff auf Dateien im csv
-Format [5]
erlaubt. Dieses Format wird häufig von Tabellenkalkulationsprogrammen wie zum
Beispiel Microsoft Excel
oder Calc
aus OpenOffice
bzw.
LibreOffice
benutzt. Hat man solche Programme bei der Erfassung der Daten
verwendet, so ist es sinnvoll, sich das csv
-Modul [6] anzusehen.
Bei einer aufwendigen Übergabe von Parametern an ein Programm kann auch das
ConfigParser
-Modul [7] von Interesse sein, das mit Dateien im
INI
-Format umgehen kann. Dabei werden Parameter in Name-Wert-Paaren
beschrieben, wobei eine Unterteilung in Abschnitte möglich ist.
Footnotes
[1] | Zum Ursprung dieses Namens siehe RFC 3092. |
[2] | Dieses Modul ist unter dem Titel argparse – Parser for command-line options, arguments and sub-commands in der Python-Dokumentation beschrieben. |
[3] | Hierzu und zu den folgenden Überlegungen zur Zeichenkodierung sei auf den Anhang Unicode hingewiesen. |
[4] | Die Verwendung von spam in Python-Beispielen als Name ohne spezifische Bedeutung ist
ein Verweis auf einen Sketch der Komikergruppe Monty Python (siehe Wikipedia: Spam-Sketch). |
[5] | csv steht für comma separated values, wobei allerdings kein verbindlicher Standard
existiert. Beispielsweise können Felder genauso gut durch Kommas wie durch Strichpunkte getrennt
sein. |
[6] | Dieses Modul ist unter dem Titel csv — CSV File Reading and Writing in der Python-Dokumentation beschrieben. |
[7] | Dieses Modul ist unter dem Titel ConfigParser — Configuration file parser in der Python-Dokumentation beschrieben. |