2.34. 動的なコード

Pythonのソースコードを表す文字列を受け取って実行する組み込み関数が3つあります。eval()exec()compile() です。これらを組み合わせると、実行時にコードが さらに別の コードを構築して実行できるようになります。これはまれにちょうど適切なツールとなりますが、それよりはるかに多くの場合、バグやセキュリティホールの原因になります。

警告

これらの関数は任意のPythonを実行します。evalexec にユーザー入力(ユーザーが編集できるファイルからの文字列、ネットワーク経由で受け取ったペイロード、REPLプロンプトで入力された値)を渡すと、その入力は呼び出し側スクリプトができることなら何でも、デバイス上のあらゆるファイルの削除に至るまで実行できてしまいます。意図的に使い、自動的に実行されるコードの中では決して使わず、自分が制御できないデータに対しては決して使わないでください。

2.34.1. eval

eval() は単一の を実行し、その値を返します。

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

式はデフォルトで呼び出し側のグローバルとローカルを参照します。だからこそ2番目の例で name が解決されます。明示的に辞書を渡すと、評価をサンドボックス化できます。

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

サンドボックス化しても eval は危険です。そうしたサンドボックスを抜け出す手法はよく知られています。信頼できない入力に対して、空の __builtins__ というトリックだけに頼らないでください。

2.34.2. exec

exec() は単一の式ではなく ブロック のコード(文、関数定義、ループ)を実行し、None を返します。

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

出力:

0
1
2

ブロックは、ローカルスコープとグローバルスコープに関するいくつかの注意点はあるものの、後で利用可能になる名前を定義できます。関数内での exec は、書き手の期待通りに動作することはめったにありません。必要な場合は、モジュールレベルで実行してください。

2.34.3. compile

compile() は、ソース文字列を、後で eval()exec() に渡せる コードオブジェクト に変換します。同じソースを何度も実行する場合に使ってください。解析は一度だけ行われ、実行が高速になります。

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

出力:

0
1
4
9
16

2番目の引数は、コードが例外を送出した場合にトレースバックに表示されるラベルです。3番目の引数は、単一の式なら "eval"、ブロックなら "exec"、結果を表示する対話形式の文なら "single" です。

2.34.4. これらをいつ使うべきか

ほとんど使うべきではありません。初心者が想像するほとんどのユースケースには、より安全な代替手段があります。

  • 設定ファイルの読み込み。 json を使ってください。構造化データであり、実行は行われません。

  • ユーザーが入力した数値の評価。 int() / float() を使って解析し、その後に算術演算を行ってください。ユーザーが本当に数式を入力する必要がある場合は、eval ではなく小さな式パーサを使ってください。

eval / exec / compile がどうしても必要な場合は、呼び出し箇所を隔離し、これから実行されるまさにその文字列をログに記録し、そのソースをコード中で最も疑わしいものとして扱ってください。