Manche Sprachen melden Fehler über einen besonderen Rückgabewert. Dann muss nach jedem Aufruf nachgesehen werden, und eine vergessene Kontrolle rechnet stillschweigend mit falschen Werten weiter. Python geht den anderen Weg: Eine Funktion liefert entweder ein sinnvolles Ergebnis oder wirft eine Exception. Der Aufrufer darf sich auf das Ergebnis verlassen.
R steht dazwischen. Schwere Fehler brechen ab (stop()), viele Probleme erzeugen aber nur eine Warnung und liefern NA. Wer in R sauber arbeiten will, muss deshalb Warnungen ernst nehmen; wer von R kommt, muss sich in Python daran gewöhnen, dass dieselbe Situation einen Abbruch erzeugt.
Die vier Bestandteile
try: wert =int(text) # was scheitern kann, und nur dasexceptValueErroras fehler: wert =None# Behandlung des erwarteten Fehlerselse: wert = wert *2# was vom Gelingen abhängtfinally: datei.close() # Aufräumen, in jedem Fall
Teil
wann er läuft
wofür
R-Gegenstück in tryCatch()
try
immer
der Code, der scheitern kann
der erste Ausdruck
except Typ
nur bei passendem Fehler
Behandlung
error = function(e), warning = function(w)
else
nur wenn nichts geworfen wurde
Folgecode
keines, steht einfach im Ausdruck
finally
in jedem Fall
Aufräumen
finally = ...
Im try soll möglichst wenig stehen. Alles darin ist Kandidat für den except-Zweig; steht dort auch Folgecode, könnte dessen Fehler versehentlich als der erwartete behandelt werden. Dafür gibt es else.
Die Fehlerhierarchie
Fehler sind Klassen und erben voneinander. except ValueError fängt auch jeden Fehler, der von ValueError abgeleitet ist. Die häufigsten:
Fehler
typischer Anlass
ValueError
richtiger Typ, falscher Inhalt: int("x")
TypeError
falscher Typ: 100 / "text"
KeyError, IndexError
fehlender Schlüssel, Index jenseits des Endes
ZeroDivisionError
Division durch null
FileNotFoundError
Datei fehlt; erbt von OSError
UnicodeDecodeError
Datei mit falschem Encoding gelesen; erbt von ValueError
Einen eigenen Fehler löst man mit raise aus, etwa raise ValueError(f"n muss positiv sein, war {n}"). Die Meldung nennt den tatsächlichen Wert; das spart später die Suche.
Dateien, Modi und Encoding
Modus
bedeutet
"r"
lesen, Voreinstellung
"w"
schreiben, überschreibt eine vorhandene Datei
"a"
anhängen
"x"
neu anlegen, scheitert, wenn die Datei existiert
"b" dazu
binär, etwa "rb", "wb": Bytes statt Text, kein Encoding
Der with-Block schliesst die Datei auch dann, wenn im Block ein Fehler auftritt. Ohne Encoding-Angabe verwendet Python die Voreinstellung des Betriebssystems, und die ist unter Windows eine andere als unter Linux. Deshalb gilt ohne Ausnahme: encoding="utf-8" angeben, beim Schreiben wie beim Lesen.
Zeilenenden. Im Textmodus schreibt Python ein \n unter Linux als ein Byte, unter Windows als zwei (\r\n). Dieselbe Datei ist deshalb je nach System verschieden gross. Wer das nicht will, öffnet mit newline="\n".
keine Tupel, keine Datumstypen, Schlüssel nur als Text
CSV
Tabellen
Mensch und Tabellenkalkulation
verliert jede Typinformation
Pickle, saveRDS
fast beliebige Objekte der Sprache
nur dieselbe Sprache
Pickle aus fremder Quelle ist unsicher
Pickle darf niemals aus unbekannter Quelle geladen werden. Beim Laden wird Code ausgeführt, eine präparierte Datei kann also beliebige Befehle absetzen. Für eigene Zwischenstände ist es in Ordnung, für den Austausch nicht.
Fünf Eingaben aus einem Formular: "42", "3.5", "keine Angabe", eine leere Zeichenkette und "1e3". Sie sollen in Zahlen verwandelt werden, nicht verwertbare Einträge als fehlend, und danach soll der Mittelwert der gültigen Werte stehen.
nicht verwertbar: could not convert string to float: 'keine Angabe'
nicht verwertbar: could not convert string to float: ''
print(werte)
[42.0, 3.5, nan, nan, 1000.0]
gueltig = [w for w in werte if w == w] # nan ist sich selbst nicht gleichprint(len(gueltig), sum(gueltig) /len(gueltig))
3 348.5
Output Zeile für Zeile
Eingabe
Python
R
Erklärung
"42"
42.0
42
"3.5"
3.5
3.5
"keine Angabe"
ValueError, gefangen, dann nan
NA mit Warnung
Python bricht ab, R warnt und rechnet weiter. Die Warnung lautet NAs introduced by coercion.
""
ValueError, dann nan
NA
Auch die leere Eingabe ist keine Zahl.
"1e3"
1000.0
1000
Die wissenschaftliche Schreibweise verstehen beide.
gültige Werte
3
3
Mittelwert
348.5
348.5
(42 + 3.5 + 1000) / 3
Die Python-Meldung could not convert string to float: 'keine Angabe' nennt den tatsächlichen Wert. Das ist der Grund, sie auszugeben statt eines eigenen Textes. Der Trick w == w sortiert nan aus, weil nan sich selbst nicht gleich ist; lesbarer ist math.isnan(w).
Interpretation und Ergebnissatz
Das Ergebnis ist in beiden Sprachen dasselbe, der Weg dorthin nicht. In R muss man die Warnung bemerken, sonst verschwinden Eingaben still als NA. Das suppressWarnings ist hier nur vertretbar, weil die Warnung unmittelbar davor ausgewertet wurde. Der Mittelwert von 348.5 zeigt nebenbei, was ein einzelner grosser Wert anrichtet; ob 1e3 ein Tippfehler war, beantwortet keine Fehlerbehandlung.
Von fünf Eingaben waren drei als Zahl verwertbar, ihr Mittelwert beträgt 348.5. Die beiden anderen führen in Python zu einem ValueError, der gefangen und als fehlender Wert eingetragen wird; R liefert NA und eine Warnung.
Frage und Datenlage
Zwei Fassungen einer Division von 100 durch einen Wert: eine fängt jeden Fehler, eine nur die Division durch null. Aufgerufen mit 4, mit 0 und mit dem Text "text", der einen Defekt weiter oben im Programm anzeigt.
def riskant(wert):try:return100/ wertexceptException:returnNone# verdeckt jeden Fehlerdef genau(wert):try:return100/ wertexceptZeroDivisionError:returnfloat("inf") # nur der erwartete Fallprint(riskant(4), riskant(0), riskant("text"))
TypeError unsupported operand type(s) for /: 'int' and 'str'
Output Zeile für Zeile
Aufruf
riskant
genau
R
Erklärung
4
25.0
25.0
25
0
None
inf
Inf
Python wirft ZeroDivisionError. R rechnet nach dem Gleitkommastandard: 100 / 0 ist Inf, -100 / 0 ist -Inf, 0 / 0 ist NaN, alles ohne Fehler.
"text"
None
TypeError
Fehler non-numeric argument to binary operator
Der Defekt ist ein falscher Typ. riskant macht ihn unsichtbar, genau lässt ihn durch.
Die Meldung von genau("text") lautet unsupported operand type(s) for /: 'int' and 'str' und zeigt sofort, wo das Problem liegt: Ein Text ist dort angekommen, wo eine Zahl erwartet wurde.
Interpretation und Ergebnissatz
riskant gibt für die Division durch null und für den Typfehler dasselbe zurück, None. Wer später ein None findet, kann nicht mehr sagen, ob ein erwarteter Grenzfall oder ein Programmfehler dahintersteckt. Die genaue Fassung trennt beides: Den erwarteten Fall behandelt sie, den unerwarteten lässt sie laut scheitern. R braucht für die Division gar keine Behandlung, muss dafür aber Inf und NaN in späteren Rechnungen im Blick behalten.
Die breite Fassung liefert für "text" stillschweigend None und verdeckt damit einen Typfehler; die genaue Fassung fängt nur ZeroDivisionError und meldet den Typfehler. In R ergibt 100 / 0 ohne Fehler Inf.
Frage und Datenlage
In welcher Reihenfolge laufen try, except, else und finally, wenn die Eingabe gültig beziehungsweise ungültig ist, und läuft finally auch nach einem return? Danach ein eigener Fehler Messfehler für negative Werte, angewendet auf 4, -1, 9.
21 42 ['try', 'else', 'finally']
x None ['try', 'except', 'finally']
class Messfehler(ValueError):passdef pruefe_wert(x):if x <0:raise Messfehler(f"negativer Wert: {x}")return x **0.5ergebnisse = []for x in [4, -1, 9]:try: ergebnisse.append(pruefe_wert(x))except Messfehler as fehler:print(fehler) ergebnisse.append(None)
negativer Wert: -1
print(ergebnisse)
[2.0, None, 3.0]
print(issubclass(Messfehler, ValueError))
True
Output Zeile für Zeile
Eingabe
Ergebnis
durchlaufene Teile
Erklärung
"21"
42
try, else, finally
else gibt 42 zurück, und trotzdem läuft danach finally.
"x"
None
try, except, finally
Auch nach dem return im except läuft finally.
Zeile
Python
R
Erklärung
finally in R
finally laeuft, dann "gefangen"
Der finally-Teil läuft nach dem Handler, noch bevor der Wert angezeigt wird.
Meldung des eigenen Fehlers
negativer Wert: -1
"negativer Wert: -1"
Ergebnisse für 4, −1, 9
[2.0, None, 3.0]
2 NA 3
Die Schleife läuft weiter; nur der eine Wert fehlt.
issubclass(Messfehler, ValueError)
True
Wer allgemein ValueError fängt, fängt auch den eigenen Fehler. In R übernimmt der Klassenvektor c("messfehler", "error", "condition") dieselbe Rolle.
Interpretation und Ergebnissatz
finally ist der Ort für alles, was unabhängig vom Ausgang geschehen muss: eine Datei schliessen, eine Verbindung trennen, einen Zeitstempel protokollieren. Ein eigener Fehlertyp macht die Behandlung genau: Der Aufrufer fängt Messfehler und weiss, dass ein unplausibler Messwert gemeint ist und kein Programmfehler. Weil er von ValueError erbt, funktioniert zugleich jeder Code, der schon vorher ValueError gefangen hat.
finally lief in beiden Fällen, auch nach einem return. Der eigene Messfehler meldete negativer Wert: -1, die Auswertung lief weiter und lieferte [2.0, None, 3.0]; als Unterklasse von ValueError wird er auch von allgemeineren Handlern erfasst.
Frage und Datenlage
Drei Zeilen mit Name und Wert, eine davon mit Umlaut und Mikrozeichen (Grösse;12 µm), werden in eine Textdatei geschrieben und wieder gelesen. Was passiert beim Lesen mit falschem Encoding?
37 Zeichen, aber ö und µ brauchen in UTF-8 je zwei Byte. Unter Windows wären es 42, weil jedes der drei \n als \r\n geschrieben wird.
gelesene Zeilen
Drossel, Ventil, Grösse mit ihren Werten
rstrip("\n") entfernt den Zeilenumbruch, split(";") trennt Name und Wert.
datei.closed
True
Nach dem with-Block ist die Datei geschlossen, ohne close().
Zeichen und Bytes
37 und 39
len zählt Zeichen, die kodierte Fassung Bytes.
R: Zeichen und Bytes der dritten Zeile
12 und 14
Dieselbe Differenz von zwei Byte für ö und µ.
mit Latin-1 gelesen
Grösse;12 µm
Kein Fehler, sondern Kauderwelsch: Jedes Zwei-Byte-Zeichen wird als zwei Latin-1-Zeichen gelesen. In R mit file(..., encoding = "latin1") genauso.
mit ASCII gelesen
UnicodeDecodeError
ASCII kennt keine Bytes über 127 und bricht ab.
Interpretation und Ergebnissatz
Das falsche Encoding ist gefährlicher, wenn es keinen Fehler auslöst. Latin-1 kann jede Bytefolge lesen, deshalb entsteht still ein verdorbener Text, der vielleicht erst in einem Bericht auffällt. Die Merkmale ö für ö und µ für µ sind das Erkennungszeichen: Eine UTF-8-Datei wurde als Latin-1 gelesen. Die Abhilfe ist die ausdrückliche Angabe encoding="utf-8" auf beiden Seiten.
Die Datei umfasst 37 Zeichen und unter Linux 39 Byte, weil ö und µ in UTF-8 je zwei Byte belegen. Mit Latin-1 gelesen wird aus Grösse;12 µm ohne Fehlermeldung Grösse;12 µm, mit ASCII bricht das Lesen mit einem UnicodeDecodeError ab.
Frage und Datenlage
Eine Konfiguration mit Zahl, Wahrheitswert, Liste, Tupel und einem ganzzahligen Schlüssel wird als JSON und als Pickle gespeichert und wieder gelesen. Kommt dasselbe zurück? Dazu eine CSV-Datei mit Kennungen 007 und 012.
True wird zu true, beide Sprachen schreiben dasselbe Format.
Tupel (0, 100)
wird zu [0, 100]
JSON kennt nur Listen.
Schlüssel 7
wird zu '7'
JSON-Schlüssel sind immer Text.
gleich nach dem Rücktransport?
False
FALSE
In Python wegen Tupel und Schlüssel. In R, weil fromJSON den Bereich 0, 100 als ganze Zahlen (int) zurückliest, die vorher Gleitkommazahlen (num) waren.
Datum
TypeError, als Text "2026-03-01" gespeichert
Der übliche Ausweg ist das ISO-Format als Zeichenkette.
Pickle, saveRDS
True
TRUE
Das sprachgebundene Format bringt das Objekt unverändert zurück.
CSV ohne Typangabe
[7, 12]
7 12
Die Kennungen werden als Zahlen erkannt, die führenden Nullen sind weg.
CSV mit Typangabe
['007', '012']
"007" "012"
dtype={"id": str} beziehungsweise colClasses hält sie als Text.
Interpretation und Ergebnissatz
Jedes sprachneutrale Format ist ein Kompromiss: Es kann nur ausdrücken, was alle Sprachen verstehen, und verliert den Rest. Der Verlust ist leise; der Vergleich zurueck == konfig ist die einfachste Probe. Bei CSV ist der Schaden am grössten, weil gar keine Typen mitreisen und aus einer Kennung 007 beim Zurückschreiben die Zahl 7 wird. Für den Austausch deshalb JSON oder CSV mit ausdrücklichen Typen, für eigene Zwischenstände Pickle oder saveRDS.
Nach dem Rücktransport über JSON ist die Konfiguration in beiden Sprachen nicht mehr identisch: Python macht aus dem Tupel eine Liste und aus dem Schlüssel 7 den Text '7', R aus Gleitkomma- ganze Zahlen. Pickle und saveRDS bringen das Objekt unverändert zurück, CSV verliert ohne Typangabe die führenden Nullen der Kennungen.
Verständnisfragen
Warum ist except Exception fast immer zu breit?
Es fängt auch Fehler ab, die einen Defekt im eigenen Code anzeigen
Richtig, etwa den Typfehler aus Beispiel 2, der als None endet. Solche Fehler sollen laut scheitern, statt still behandelt zu werden.
Es ist langsamer als ein genauer Typ
Die Geschwindigkeit spielt keine Rolle.
Es funktioniert nur bei eingebauten Fehlern
Es fängt gerade alles, und das ist das Problem.
Wozu dient der else-Zweig, wenn der Code auch am Ende des try stehen könnte?
Er ist Geschmackssache
Er trennt zwei Dinge, die sonst vermischt werden.
Weil im try möglichst wenig stehen soll
Richtig. Alles darin ist Kandidat für den except-Zweig. Steht dort auch Folgecode, könnte dessen Fehler versehentlich als der erwartete behandelt werden.
Er läuft auch im Fehlerfall
Das tut finally. else läuft nur, wenn nichts geworfen wurde.
Eine Funktion gibt im try mit return einen Wert zurück, im finally steht protokoll.append("finally"). Wird der Eintrag geschrieben?
Ja, finally läuft auch nach einem return
Richtig, wie das Protokoll ['try', 'else', 'finally'] in Beispiel 3 zeigt. Deshalb ist finally der sichere Ort zum Aufräumen.
Nein, return beendet die Funktion sofort
Es beendet sie, aber erst nach dem finally-Block.
Nur wenn ein Fehler aufgetreten ist
Das wäre except.
Eine Textdatei zeigt nach dem Einlesen Grösse statt Grösse. Was ist passiert?
Eine UTF-8-Datei wurde als Latin-1 gelesen
Richtig. Das ö belegt in UTF-8 zwei Byte, und Latin-1 macht daraus zwei Zeichen. Abhilfe: encoding="utf-8" beim Lesen angeben.
Die Datei ist beschädigt
Die Bytes sind in Ordnung, nur ihre Deutung ist falsch.
Python unterstützt keine Umlaute
Python arbeitet intern mit Unicode; das Problem entsteht erst an der Grenze zur Datei.
Wann ist Pickle gegenüber JSON gerechtfertigt?
Für kurzlebige eigene Zwischenstände
Richtig, etwa ein zwischengespeichertes Modellobjekt. Es bringt Tupel und ganzzahlige Schlüssel unverändert zurück, JSON nicht. Sobald die Datei weitergegeben oder von einer anderen Sprache gelesen werden soll, ist JSON richtig.
Immer, es kann mehr als JSON
Es kann mehr und ist dafür an Python gebunden und aus fremder Quelle gefährlich, weil beim Laden Code ausgeführt wird.
Nie, es ist grundsätzlich unsicher
Unsicher ist nur das Laden fremder Dateien.
Eine CSV-Datei enthält die Spalte id mit Werten wie 007. Was passiert beim Einlesen ohne Typangabe und Zurückschreiben?
Die führenden Nullen gehen verloren, aus 007 wird 7
Richtig, wie Beispiel 5 in beiden Sprachen zeigt. CSV trägt keine Typinformation, deshalb muss der Typ beim Einlesen vorgegeben werden.
Nichts, Text bleibt Text
Ob es Text bleibt, entscheidet die Typerkennung, und die sieht hier eine Zahl.
Der Import bricht mit einem Fehler ab
Er gelingt klaglos, und genau das macht den Fall gefährlich.