Fehlerbehandlung und Dateizugriff

Programmiergrundlagen
Python
R
Tooling
Exceptions gezielt abfangen, Dateien lesen und schreiben.

Kernideen

  • Eine Funktion liefert ein brauchbares Ergebnis oder bricht ab. Wer den Fehler behandeln will, tut es an einer Stelle mit try und except.
  • Nur den erwarteten Fehlertyp fangen. except Exception verschluckt auch Defekte im eigenen Code.
  • else läuft nur bei Erfolg, finally immer, auch nach einem return.
  • Eigene Fehler sind Klassen, die von einem passenden eingebauten Fehler erben.
  • Dateien öffnet man mit with, damit sie auch im Fehlerfall geschlossen werden, und immer mit encoding="utf-8".
  • JSON ist sprachneutral und verliert Typen, Pickle und saveRDS behalten sie und sind an ihre Sprache gebunden.

Erklärung

Vorwissen: Funktionen und Module in Python für Funktionen, deren Fehlerverhalten hier zum Thema wird, und Objektorientierung in Python für eigene Fehlerklassen; sie sind gewöhnliche Klassen mit Vererbung. Wie Tabellen eingelesen werden, steht unter pandas.

Fehler als Ausnahme statt als Rückgabewert

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 das
except ValueError as fehler:
    wert = None                # Behandlung des erwarteten Fehlers
else:
    wert = wert * 2            # was vom Gelingen abhängt
finally:
    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".

Serialisierung: drei Formate, drei Zwecke

Format speichert lesbar für Grenze
JSON Zahlen, Text, Wahrheitswerte, Listen, Dictionaries Mensch und andere Sprachen 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.

Kurz nachgeschlagen

Aufgabe R Python
erwarteten Fehler abfangen tryCatch(ausdruck, error = function(e) NA) try: ... except ValueError: ...
Warnung abfangen tryCatch(..., warning = function(w) ...) warnings.catch_warnings()
Fehler auslösen stop("Meldung") raise ValueError("Meldung")
Meldung eines Fehlers conditionMessage(e) str(fehler)
Text lesen readLines(pfad, encoding = "UTF-8") pathlib.Path(pfad).read_text(encoding="utf-8")
Text schreiben writeLines(zeilen, pfad) with open(pfad, "w", encoding="utf-8") as f:
nicht überschreiben if (!file.exists(pfad)) writeLines(...) Modus "x"
JSON jsonlite::write_json(), read_json() json.dump(), json.load()
sprachgebundene Sicherung saveRDS(), readRDS() pickle.dump(), pickle.load()

Beispiele

Frage und Datenlage

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.

Rechnung

eingaben <- c("42", "3.5", "keine Angabe", "", "1e3")

tryCatch(as.numeric("keine Angabe"),
         warning = function(w) conditionMessage(w))
[1] "NAs introduced by coercion"
werte <- suppressWarnings(as.numeric(eingaben))
werte
[1]   42.0    3.5     NA     NA 1000.0
c(gueltig = sum(!is.na(werte)), mittel = mean(werte, na.rm = TRUE))
gueltig  mittel 
    3.0   348.5 
eingaben = ["42", "3.5", "keine Angabe", "", "1e3"]
werte = []
for eingabe in eingaben:
    try:
        werte.append(float(eingabe))
    except ValueError as fehler:
        print(f"nicht verwertbar: {fehler}")
        werte.append(float("nan"))
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 gleich
print(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.

Rechnung

c(100 / 4, 100 / 0, -100 / 0, 0 / 0)
[1]   25  Inf -Inf  NaN
riskant <- function(wert) tryCatch(100 / wert, error = function(e) NULL)
is.null(riskant("text"))
[1] TRUE
tryCatch(100 / "text", error = function(e) conditionMessage(e))
[1] "non-numeric argument to binary operator"
def riskant(wert):
    try:
        return 100 / wert
    except Exception:
        return None                  # verdeckt jeden Fehler

def genau(wert):
    try:
        return 100 / wert
    except ZeroDivisionError:
        return float("inf")          # nur der erwartete Fall

print(riskant(4), riskant(0), riskant("text"))
25.0 None None
print(genau(4), genau(0))
25.0 inf
try:
    genau("text")
except TypeError as fehler:
    print(type(fehler).__name__, fehler)
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.

Rechnung

tryCatch(stop("kaputt"),
         error = function(e) "gefangen",
         finally = cat("finally laeuft\n"))
finally laeuft
[1] "gefangen"
messfehler <- function(x) {
  structure(class = c("messfehler", "error", "condition"),
            list(message = paste("negativer Wert:", x), call = NULL))
}
pruefe_wert <- function(x) {
  if (x < 0) stop(messfehler(x))
  sqrt(x)
}
sapply(c(4, -1, 9),
       function(x) tryCatch(pruefe_wert(x), messfehler = function(e) NA))
[1]  2 NA  3
tryCatch(pruefe_wert(-1), messfehler = function(e) conditionMessage(e))
[1] "negativer Wert: -1"
def lies_zahl(text, protokoll):
    try:
        protokoll.append("try")
        wert = int(text)
    except ValueError:
        protokoll.append("except")
        return None
    else:
        protokoll.append("else")
        return wert * 2
    finally:
        protokoll.append("finally")

for eingabe in ["21", "x"]:
    schritte = []
    ergebnis = lies_zahl(eingabe, schritte)
    print(eingabe, ergebnis, schritte)
21 42 ['try', 'else', 'finally']
x None ['try', 'except', 'finally']
class Messfehler(ValueError):
    pass

def pruefe_wert(x):
    if x < 0:
        raise Messfehler(f"negativer Wert: {x}")
    return x ** 0.5

ergebnisse = []
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?

Rechnung

pfad <- tempfile(fileext = ".txt")
writeLines(c("Drossel;12.4", "Ventil;9.8", "Grösse;12 µm"), pfad)
file.size(pfad)
[1] 39
zeilen <- readLines(pfad, encoding = "UTF-8")
sapply(strsplit(zeilen, ";"), `[`, 1)
[1] "Drossel" "Ventil"  "Grösse" 
c(zeichen = nchar(zeilen[3]), bytes = nchar(zeilen[3], type = "bytes"))
zeichen   bytes 
     12      14 
verbindung <- file(pfad, encoding = "latin1")
readLines(verbindung)[3]
[1] "Grösse;12 µm"
pfad = arbeitsordner / "messung.txt"
with open(pfad, "w", encoding="utf-8") as datei:
    _ = datei.write("Drossel;12.4\nVentil;9.8\nGrösse;12 µm\n")
print(pfad.stat().st_size)
39
with open(pfad, encoding="utf-8") as datei:
    for zeile in datei:
        name, wert = zeile.rstrip("\n").split(";")
        print(f"{name:10s}|{wert}")
Drossel   |12.4
Ventil    |9.8
Grösse    |12 µm
print(datei.closed)
True
text = pfad.read_text(encoding="utf-8")
print(len(text), len(text.encode("utf-8")))
37 39
print(pfad.read_text(encoding="latin-1").splitlines()[2])
Grösse;12 µm
try:
    pfad.read_text(encoding="ascii")
except UnicodeDecodeError as fehler:
    print(type(fehler).__name__)
UnicodeDecodeError

Output Zeile für Zeile

Zeile Ausgabe Erklärung
Dateigrösse 39 Byte 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.

Rechnung

konfig <- list(schwelle = 12.5, aktiv = TRUE, orte = c("Bern", "Chur"),
               bereich = c(0, 100))

text <- jsonlite::toJSON(konfig, auto_unbox = TRUE)
text
{"schwelle":12.5,"aktiv":true,"orte":["Bern","Chur"],"bereich":[0,100]} 
zurueck <- jsonlite::fromJSON(text)
str(zurueck)
List of 4
 $ schwelle: num 12.5
 $ aktiv   : logi TRUE
 $ orte    : chr [1:2] "Bern" "Chur"
 $ bereich : int [1:2] 0 100
identical(zurueck, konfig)
[1] FALSE
pfad <- tempfile(fileext = ".rds")
saveRDS(konfig, pfad)
identical(readRDS(pfad), konfig)
[1] TRUE
csv_text <- "id,wert\n007,1.5\n012,2.0"
read.csv(text = csv_text)$id
[1]  7 12
read.csv(text = csv_text, colClasses = c(id = "character"))$id
[1] "007" "012"
konfig = {"schwelle": 12.5, "aktiv": True, "orte": ["Bern", "Chur"],
          "bereich": (0, 100), 7: "sieben"}

text = json.dumps(konfig, ensure_ascii=False)
print(text)
{"schwelle": 12.5, "aktiv": true, "orte": ["Bern", "Chur"], "bereich": [0, 100], "7": "sieben"}
zurueck = json.loads(text)
print(zurueck == konfig, zurueck["bereich"], repr(list(zurueck)[-1]))
False [0, 100] '7'
try:
    json.dumps({"zeitpunkt": datetime.date(2026, 3, 1)})
except TypeError as fehler:
    print(type(fehler).__name__)
TypeError
print(json.dumps({"zeitpunkt": datetime.date(2026, 3, 1).isoformat()}))
{"zeitpunkt": "2026-03-01"}
pfad = arbeitsordner / "zwischenstand.pkl"
with open(pfad, "wb") as datei:                  # Binaermodus
    pickle.dump(konfig, datei)
with open(pfad, "rb") as datei:
    print(pickle.load(datei) == konfig)
True
csv_text = "id,wert\n007,1.5\n012,2.0\n"
print(pd.read_csv(io.StringIO(csv_text))["id"].tolist())
[7, 12]
print(pd.read_csv(io.StringIO(csv_text), dtype={"id": str})["id"].tolist())
['007', '012']

Output Zeile für Zeile

Schritt Python R Erklärung
JSON-Text {"schwelle": 12.5, "aktiv": true, ...} {"schwelle":12.5,"aktiv":true,...} 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.

Verlinkte Ressourcen