7. Ein- und Ausgaben

Es gibt verschiedene Möglichkeiten, die Ausgabe eines Programms darzustellen; Daten können in einer für Menschen lesbaren Form ausgegeben oder zur späteren Verwendung in eine Datei geschrieben werden. Dieses Kapitel behandelt einige dieser Möglichkeiten.

7.1. Ausgefallenere Ausgabeformatierung

Bisher haben wir zwei Möglichkeiten zum Ausgeben von Werten kennengelernt: Ausdrucksanweisungen und die print()-Funktion. (Eine dritte Möglichkeit ist die Verwendung der write()-Methode von Datei-Objekten; die Standardausgabedatei kann als sys.stdout referenziert werden. Weitere Informationen hierzu finden Sie in der Bibliotheksreferenz.)

Oftmals möchten Sie die Formatierung Ihrer Ausgabe stärker kontrollieren, als nur durch die Ausgabe von durch Leerzeichen getrennten Werten. Es gibt mehrere Möglichkeiten, die Ausgabe zu formatieren.

  • Um formatierte Zeichenkettenliterale zu verwenden, stellen Sie einer Zeichenkette ein f oder F voran, bevor Sie die Anführungszeichen oder dreifachen Anführungszeichen setzen. Innerhalb dieser Zeichenkette können Sie einen Python-Ausdruck zwischen { und }-Zeichen schreiben, der sich auf Variablen oder literale Werte beziehen kann.

    >>> year = 2016
    >>> event = 'Referendum'
    >>> f'Results of the {year} {event}'
    'Results of the 2016 Referendum'
    
  • Die str.format()-Methode von Zeichenketten erfordert mehr manuellen Aufwand. Sie verwenden immer noch { und }, um zu markieren, wo eine Variable eingefügt werden soll, und können detaillierte Formatierungsanweisungen angeben, müssen aber auch die zu formatierenden Informationen bereitstellen. Im folgenden Codeblock sehen Sie zwei Beispiele, wie Variablen formatiert werden.

    >>> yes_votes = 42_572_654
    >>> total_votes = 85_705_149
    >>> percentage = yes_votes / total_votes
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes  49.67%'
    

    Beachten Sie, wie die yes_votes mit Leerzeichen und einem Minuszeichen nur für negative Zahlen aufgefüllt werden. Das Beispiel gibt auch percentage multipliziert mit 100 aus, mit 2 Dezimalstellen und gefolgt von einem Prozentzeichen (siehe Mini-Sprache für Formatangaben für Details).

  • Schließlich können Sie die gesamte Zeichenkettenverwaltung selbst übernehmen, indem Sie Zeichenketten-Slicing und Verkettungsoperationen verwenden, um jedes beliebige Layout zu erstellen, das Sie sich vorstellen können. Der Zeichentyp hat einige Methoden, die nützliche Operationen zum Auffüllen von Zeichenketten auf eine bestimmte Spaltenbreite durchführen.

Wenn Sie keine ausgefallene Ausgabe benötigen, sondern nur eine schnelle Anzeige einiger Variablen zu Debugging-Zwecken wünschen, können Sie jeden Wert mit den Funktionen repr() oder str() in eine Zeichenkette umwandeln.

Die Funktion str() soll Darstellungen von Werten zurückgeben, die für Menschen recht gut lesbar sind, während repr() Darstellungen generieren soll, die vom Interpreter gelesen werden können (oder einen SyntaxError erzwingen, wenn keine entsprechende Syntax vorhanden ist). Für Objekte, die keine spezielle Darstellung für den menschlichen Konsum haben, gibt str() den gleichen Wert zurück wie repr(). Viele Werte, wie Zahlen oder Strukturen wie Listen und Dictionaries, haben die gleiche Darstellung, egal welche Funktion verwendet wird. Zeichenketten haben insbesondere zwei unterschiedliche Darstellungen.

Einige Beispiele

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
>>> hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
>>> repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

Das Modul string enthält Unterstützung für einen einfachen Vorlagenansatz, der auf regulären Ausdrücken basiert, über string.Template. Dies bietet eine weitere Möglichkeit, Werte in Zeichenketten einzufügen, indem Platzhalter wie $x verwendet und diese durch Werte aus einem Dictionary ersetzt werden. Diese Syntax ist einfach zu verwenden, bietet aber viel weniger Kontrolle über die Formatierung.

7.1.1. Formatierte Zeichenkettenliterale

Formatierte Zeichenkettenliterale (kurz f-Strings genannt) erlauben es Ihnen, den Wert von Python-Ausdrücken innerhalb einer Zeichenkette einzubetten, indem Sie die Zeichenkette mit f oder F versehen und Ausdrücke als {expression} schreiben.

Ein optionaler Format-Spezifizierer kann auf den Ausdruck folgen. Dies ermöglicht eine größere Kontrolle darüber, wie der Wert formatiert wird. Das folgende Beispiel rundet Pi auf drei Nachkommastellen:

>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.

Eine Ganzzahl nach dem ':' bewirkt, dass dieses Feld eine Mindestanzahl von Zeichen breit ist. Dies ist nützlich, um Spalten auszurichten.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

Andere Modifikatoren können verwendet werden, um den Wert umzuwandeln, bevor er formatiert wird. '!a' wendet ascii() an, '!s' wendet str() an und '!r' wendet repr() an.

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

Der =-Spezifizierer kann verwendet werden, um einen Ausdruck auf den Text des Ausdrucks, ein Gleichheitszeichen und dann die Darstellung des ausgewerteten Ausdrucks zu erweitern.

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

Weitere Informationen finden Sie unter selbstbeschreibende Ausdrücke für weitere Informationen zum =-Spezifizierer. Eine Referenz zu diesen Formatangaben finden Sie im Referenzhandbuch für die Mini-Sprache für Formatangaben.

7.1.2. Die str.format()-Methode

Die grundlegende Verwendung der str.format()-Methode sieht wie folgt aus:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

Die Klammern und die Zeichen darin (Formatfelder genannt) werden durch die Objekte ersetzt, die in die str.format()-Methode übergeben werden. Eine Zahl in den Klammern kann verwendet werden, um die Position des Objekts zu referenzieren, das in die str.format()-Methode übergeben wird.

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

Wenn Schlüsselwortargumente in der str.format()-Methode verwendet werden, werden ihre Werte durch den Namen des Arguments referenziert.

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

Positions- und Schlüsselwortargumente können beliebig kombiniert werden.

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
...                                                    other='Georg'))
The story of Bill, Manfred, and Georg.

Wenn Sie eine sehr lange Formatzeichenkette haben, die Sie nicht aufteilen möchten, wäre es schön, wenn Sie die zu formatierenden Variablen anhand ihres Namens statt nach Position referenzieren könnten. Dies kann geschehen, indem Sie einfach das Dictionary übergeben und eckige Klammern '[]' verwenden, um auf die Schlüssel zuzugreifen.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Dies könnte auch geschehen, indem Sie das table-Dictionary als Schlüsselwortargumente mit der **-Notation übergeben.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Dies ist besonders nützlich in Kombination mit der integrierten Funktion vars(), die ein Dictionary mit allen lokalen Variablen zurückgibt.

>>> table = {k: str(v) for k, v in vars().items()}
>>> message = " ".join([f'{k}: ' + '{' + k +'};' for k in table.keys()])
>>> print(message.format(**table))
__name__: __main__; __doc__: None; __package__: None; __loader__: ...

Als Beispiel erzeugen die folgenden Zeilen eine ordentlich ausgerichtete Spalte mit ganzen Zahlen und ihren Quadraten und Kubikzahlen.

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

Eine vollständige Übersicht über die Zeichenkettenformatierung mit str.format() finden Sie unter Formatzeichenketten-Syntax.

7.1.3. Manuelle Zeichenkettenformatierung

Hier ist dieselbe Tabelle von Quadraten und Kubikzahlen, manuell formatiert:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Beachten Sie, dass das Leerzeichen zwischen den einzelnen Spalten durch die Funktionsweise von print() hinzugefügt wurde: es fügt immer Leerzeichen zwischen seinen Argumenten ein.)

Die str.rjust()-Methode von Zeichenketten-Objekten richtet eine Zeichenkette rechtsbündig in einem Feld einer gegebenen Breite aus, indem sie sie links mit Leerzeichen auffüllt. Es gibt ähnliche Methoden str.ljust() und str.center(). Diese Methoden geben nichts aus, sie geben nur eine neue Zeichenkette zurück. Wenn die Eingabezeichenkette zu lang ist, wird sie nicht abgeschnitten, sondern unverändert zurückgegeben; dies wird Ihr Spaltenlayout durcheinanderbringen, aber das ist normalerweise besser als die Alternative, die darin bestehen würde, über einen Wert zu lügen. (Wenn Sie wirklich eine Abschneidung wünschen, können Sie immer eine Slice-Operation hinzufügen, wie in x.ljust(n)[:n].)

Es gibt eine weitere Methode, str.zfill(), die eine numerische Zeichenkette links mit Nullen auffüllt. Sie versteht Plus- und Minuszeichen.

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

7.1.4. Alte Zeichenkettenformatierung

Der %-Operator (Modulo) kann ebenfalls für die Zeichenkettenformatierung verwendet werden. Gegeben sei format % values (wobei format eine Zeichenkette ist), werden %-Konvertierungsspezifikationen in format durch null oder mehr Elemente von values ersetzt. Diese Operation wird üblicherweise als String-Interpolation bezeichnet. Zum Beispiel:

>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

Weitere Informationen finden Sie im Abschnitt Printf-Style String Formatting.

7.2. Lesen und Schreiben von Dateien

open() gibt ein Datei-Objekt zurück und wird am häufigsten mit zwei Positionsargumenten und einem Schlüsselwortargument verwendet: open(filename, mode, encoding=None)

>>> f = open('workfile', 'w', encoding="utf-8")

Das erste Argument ist eine Zeichenkette, die den Dateinamen enthält. Das zweite Argument ist eine weitere Zeichenkette, die einige Zeichen enthält, die beschreiben, wie die Datei verwendet wird. mode kann 'r' sein, wenn die Datei nur gelesen wird, 'w' für reines Schreiben (eine vorhandene Datei mit demselben Namen wird gelöscht), und 'a' öffnet die Datei zum Anhängen; alle in die Datei geschriebenen Daten werden automatisch am Ende hinzugefügt. 'r+' öffnet die Datei zum Lesen und Schreiben. Das mode-Argument ist optional; 'r' wird angenommen, wenn es weggelassen wird.

Normalerweise werden Dateien im Textmodus geöffnet, d. h. Sie lesen und schreiben Zeichenketten von und in die Datei, die in einer bestimmten Kodierung kodiert sind. Wenn encoding nicht angegeben ist, ist der Standard plattformabhängig (siehe open()). Da UTF-8 der moderne De-facto-Standard ist, wird encoding="utf-8" empfohlen, es sei denn, Sie wissen, dass Sie eine andere Kodierung verwenden müssen. Das Anhängen eines 'b' an den Modus öffnet die Datei im Binärmodus. Binärdaten werden als bytes-Objekte gelesen und geschrieben. Sie können encoding nicht angeben, wenn Sie eine Datei im Binärmodus öffnen.

Im Textmodus werden plattformspezifische Zeilenumbrüche (\n unter Unix, \r\n unter Windows) standardmäßig beim Lesen in nur \n konvertiert. Beim Schreiben im Textmodus werden Vorkommen von \n standardmäßig wieder in plattformspezifische Zeilenumbrüche konvertiert. Diese interne Änderung der Dateidaten ist für Textdateien in Ordnung, kann aber Binärdaten wie die in JPEG- oder EXE-Dateien beschädigen. Seien Sie sehr vorsichtig, wenn Sie solche Dateien im Binärmodus lesen und schreiben.

Es ist eine gute Praxis, das Schlüsselwort with zu verwenden, wenn Sie mit Datei-Objekten arbeiten. Der Vorteil ist, dass die Datei nach Abschluss ihrer Suite ordnungsgemäß geschlossen wird, selbst wenn zu irgendeinem Zeitpunkt eine Ausnahme ausgelöst wird. Die Verwendung von with ist auch viel kürzer als das Schreiben äquivalenter try-finally-Blöcke.

>>> with open('workfile', encoding="utf-8") as f:
...     read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed
True

Wenn Sie das Schlüsselwort with nicht verwenden, sollten Sie f.close() aufrufen, um die Datei zu schließen und die von ihr verwendeten Systemressourcen sofort freizugeben.

Warnung

Das Aufrufen von f.write() ohne die Verwendung des Schlüsselworts with oder das Aufrufen von f.close() **kann** dazu führen, dass die Argumente von f.write() nicht vollständig auf die Festplatte geschrieben werden, selbst wenn das Programm erfolgreich beendet wird.

Nachdem ein Datei-Objekt geschlossen wurde, entweder durch eine with-Anweisung oder durch Aufruf von f.close(), schlagen Versuche, das Datei-Objekt zu verwenden, automatisch fehl.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. Methoden von Datei-Objekten

Die restlichen Beispiele in diesem Abschnitt gehen davon aus, dass ein Datei-Objekt namens f bereits erstellt wurde.

Um den Inhalt einer Datei zu lesen, rufen Sie f.read(size) auf, was eine bestimmte Menge an Daten liest und diese als Zeichenkette (im Textmodus) oder Bytes-Objekt (im Binärmodus) zurückgibt. size ist ein optionales numerisches Argument. Wenn size weggelassen oder negativ ist, wird der gesamte Inhalt der Datei gelesen und zurückgegeben; es ist Ihr Problem, wenn die Datei doppelt so groß ist wie der Arbeitsspeicher Ihres Computers. Andernfalls werden höchstens size Zeichen (im Textmodus) oder size Bytes (im Binärmodus) gelesen und zurückgegeben. Wenn das Ende der Datei erreicht ist, gibt f.read() eine leere Zeichenkette ('') zurück.

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() liest eine einzelne Zeile aus der Datei; ein Zeilenumbruchzeichen (\n) bleibt am Ende der Zeichenkette erhalten und wird nur in der letzten Zeile der Datei weggelassen, wenn die Datei nicht mit einem Zeilenumbruch endet. Dies macht den Rückgabewert eindeutig; wenn f.readline() eine leere Zeichenkette zurückgibt, wurde das Ende der Datei erreicht, während eine leere Zeile durch '\n', eine Zeichenkette, die nur einen einzigen Zeilenumbruch enthält, repräsentiert wird.

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

Zum Lesen von Zeilen aus einer Datei können Sie über das Datei-Objekt iterieren. Dies ist speichereffizient, schnell und führt zu einfachem Code.

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

Wenn Sie alle Zeilen einer Datei in einer Liste lesen möchten, können Sie auch list(f) oder f.readlines() verwenden.

f.write(string) schreibt den Inhalt von string in die Datei und gibt die Anzahl der geschriebenen Zeichen zurück.

>>> f.write('This is a test\n')
15

Andere Objekttypen müssen konvertiert werden – entweder in eine Zeichenkette (im Textmodus) oder ein Bytes-Objekt (im Binärmodus) – bevor sie geschrieben werden können.

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

f.tell() gibt eine Ganzzahl zurück, die die aktuelle Position des Datei-Objekts in der Datei angibt, repräsentiert als Anzahl von Bytes vom Anfang der Datei (im Binärmodus) und eine undurchsichtige Zahl (im Textmodus).

Um die Position des Datei-Objekts zu ändern, verwenden Sie f.seek(offset, whence). Die Position wird berechnet, indem offset zu einem Referenzpunkt addiert wird; der Referenzpunkt wird durch das Argument whence ausgewählt. Ein whence-Wert von 0 misst vom Anfang der Datei, 1 verwendet die aktuelle Dateiposition und 2 verwendet das Ende der Datei als Referenzpunkt. whence kann weggelassen werden und standardmäßig auf 0 gesetzt werden, wobei der Anfang der Datei als Referenzpunkt verwendet wird.

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

In Textdateien (die ohne ein b in der Moduszeichenkette geöffnet wurden) sind nur Sprünge relativ zum Anfang der Datei erlaubt (die Ausnahme ist das Springen zum absoluten Dateiende mit seek(0, 2)) und die einzig gültigen offset-Werte sind diejenigen, die von f.tell() zurückgegeben wurden, oder Null. Jeder andere offset-Wert führt zu undefiniertem Verhalten.

Datei-Objekte haben einige zusätzliche Methoden, wie isatty() und truncate(), die seltener verwendet werden; konsultieren Sie das Bibliotheksreferenzhandbuch für eine vollständige Anleitung zu Datei-Objekten.

7.2.2. Speichern strukturierter Daten mit json

Zeichenketten können einfach in und aus einer Datei gelesen und geschrieben werden. Zahlen erfordern etwas mehr Aufwand, da die read()-Methode nur Zeichenketten zurückgibt, die an eine Funktion wie int() übergeben werden müssen, die eine Zeichenkette wie '123' nimmt und ihren numerischen Wert 123 zurückgibt. Wenn Sie komplexere Datentypen wie verschachtelte Listen und Dictionaries speichern möchten, wird das Parsen und Serialisieren von Hand kompliziert.

Anstatt dass Benutzer ständig Code zum Speichern komplexer Datentypen in Dateien schreiben und debuggen müssen, erlaubt Python Ihnen die Verwendung des beliebten Datenaustauschformats JSON (JavaScript Object Notation). Das Standardmodul namens json kann Python-Datenhierarchien nehmen und sie in Zeichenkettenrepräsentationen umwandeln; dieser Prozess wird als Serialisierung bezeichnet. Das Rekonstruieren der Daten aus der Zeichenkettenrepräsentation wird als Deserialisierung bezeichnet. Zwischen Serialisierung und Deserialisierung kann die Zeichenkette, die das Objekt repräsentiert, in einer Datei oder Daten gespeichert oder über eine Netzwerkverbindung an eine entfernte Maschine gesendet worden sein.

Hinweis

Das JSON-Format wird häufig von modernen Anwendungen für den Datenaustausch verwendet. Viele Programmierer sind bereits damit vertraut, was es zu einer guten Wahl für die Interoperabilität macht.

Wenn Sie ein Objekt x haben, können Sie seine JSON-Zeichenkettenrepräsentation mit einer einfachen Codezeile anzeigen:

>>> import json
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'

Eine weitere Variante der Funktion dumps(), genannt dump(), serialisiert das Objekt einfach in eine Textdatei. Wenn also f ein für das Schreiben geöffnetes Textdatei-Objekt ist, können wir Folgendes tun:

json.dump(x, f)

Um das Objekt wieder zu dekodieren, wenn f eine Binärdatei oder eine zum Lesen geöffnete Textdatei ist:

x = json.load(f)

Hinweis

JSON-Dateien müssen in UTF-8 kodiert sein. Verwenden Sie encoding="utf-8", wenn Sie eine JSON-Datei als Textdatei zum Lesen und Schreiben öffnen.

Diese einfache Serialisierungstechnik kann Listen und Dictionaries verarbeiten, aber die Serialisierung beliebiger Klasseninstanzen in JSON erfordert etwas mehr Aufwand. Die Referenz zum json-Modul enthält eine Erklärung dazu.

Siehe auch

pickle — das pickle-Modul

Im Gegensatz zu JSON ist pickle ein Protokoll, das die Serialisierung beliebig komplexer Python-Objekte ermöglicht. Daher ist es spezifisch für Python und kann nicht zur Kommunikation mit Anwendungen verwendet werden, die in anderen Sprachen geschrieben sind. Es ist außerdem standardmäßig unsicher: das Deserialisieren von Pickle-Daten aus einer nicht vertrauenswürdigen Quelle kann beliebigen Code ausführen, wenn die Daten von einem geschickten Angreifer erstellt wurden.