2.34. Dynamischer Code

Drei eingebaute Funktionen nehmen einen String mit Python-Quelltext und führen ihn aus: eval(), exec() und compile(). Zusammen ermöglichen sie es Code, zur Laufzeit weiteren Code zu konstruieren und auszuführen – was gelegentlich genau das richtige Werkzeug ist und weit häufiger eine Quelle von Fehlern und Sicherheitslücken.

Warnung

Diese Funktionen führen beliebigen Python-Code aus. Benutzereingaben an eval oder exec zu übergeben – ein String aus einer Datei, die der Benutzer bearbeiten kann, eine über das Netzwerk empfangene Nutzlast, ein an einer REPL-Eingabeaufforderung getippter Wert – erlaubt dieser Eingabe alles zu tun, was das aufrufende Skript tun könnte, bis hin zum Löschen jeder Datei auf dem Gerät. Verwenden Sie sie bewusst, niemals innerhalb von Code, der automatisch läuft, und niemals mit Daten, die Sie nicht kontrollieren.

2.34.1. eval

eval() führt einen einzelnen Ausdruck aus und gibt dessen Wert zurück:

>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'

Der Ausdruck sieht standardmäßig die Globals und Locals des Aufrufers, weshalb name im zweiten Beispiel aufgelöst wird. Das Übergeben expliziter Dictionaries ermöglicht es Ihnen, die Auswertung in eine Sandbox zu setzen:

eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})

Selbst in einer Sandbox ist eval gefährlich. Es gibt wohlbekannte Techniken, um aus solchen Sandboxes auszubrechen; verlassen Sie sich für nicht vertrauenswürdige Eingaben nicht allein auf den Trick mit dem leeren __builtins__.

2.34.2. exec

exec() führt einen Block von Code statt eines einzelnen Ausdrucks aus – Anweisungen, Funktionsdefinitionen, Schleifen – und gibt None zurück:

exec("for i in range(3): print(i)")

Ausgabe:

0
1
2

Der Block kann Namen definieren, die danach verfügbar werden, mit einigen Einschränkungen bezüglich lokalem vs. globalem Geltungsbereich. exec innerhalb einer Funktion verhält sich selten so, wie es der Schreibende erwartet; wenn Sie es benötigen, führen Sie es auf Modulebene aus.

2.34.3. compile

compile() wandelt einen Quelltext-String in ein Code-Objekt um, das später an eval() oder exec() übergeben werden kann. Verwenden Sie es, wenn derselbe Quelltext mehrfach laufen wird – das Parsen geschieht einmal, die Ausführung ist schneller:

expr = compile("x * x", "<expr>", "eval")
for x in range(5):
    print(eval(expr))

Ausgabe:

0
1
4
9
16

Das mittlere Argument ist ein Label, das in Tracebacks erscheint, wenn der Code eine Ausnahme auslöst. Das dritte Argument ist "eval" für einen einzelnen Ausdruck, "exec" für einen Block oder "single" für eine Anweisung im interaktiven Stil, die ihr Ergebnis ausgibt.

2.34.4. Wann man darauf zurückgreifen sollte

Fast nie. Die meisten Anwendungsfälle, die sich Anfänger vorstellen, haben sicherere Alternativen:

  • Eine Konfigurationsdatei lesen. Verwenden Sie json – strukturierte Daten, keine Ausführung.

  • Einen vom Benutzer getippten numerischen Wert auswerten. Verwenden Sie int() / float() zum Parsen, dann Arithmetik. Wenn der Benutzer wirklich eine Formel eingeben muss, verwenden Sie einen kleinen Ausdrucks-Parser, nicht eval.

Wenn Sie eval / exec / compile tatsächlich benötigen, isolieren Sie die Aufrufstelle, protokollieren Sie den genauen String, der ausgeführt werden soll, und behandeln Sie den Quelltext als das verdächtigste Element in Ihrem Code.