はじめに
これまで Python の eval
と exec
についてよく分かっていなかったので調べてみました。もし誤りがあれば指摘いただけたらと思います。
特に断りがない限り、Python のバージョンは 3.6.1 の内容になります。
eval とは
eval
は第1引数を式として評価します。次は簡単な例です。
>>> eval('1 + 2')
3
次は第1引数が式ではなく文(代入文)になるので、エラーになります。
>>> eval('a = 1 + 2')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1
a = 1 + 2
^
SyntaxError: invalid syntax
Python の式と文について、詳しくは次のドキュメントを参考にしてください。
また、出力結果の通り、評価の結果が戻り値となります。
>>> a = eval('1 + 2')
>>> a
3
compile について
eval
の第1引数には文字列以外を指定する事ができます。次は compile()
で生成したバイトコードを指定した例です。
>>> eval(compile('1 + 2', '<string>', 'eval'))
3
eval の第2引数、第3引数
eval
は第2引数としてグローバル、第3引数としてローカルな名前空間を指定できます。
次はローカル名前空間に変数 a
を指定した例です。
>>> eval('a * 3', {}, {'a': 2})
6
次はグローバルとローカル名前空間に同じ名前の a
を指定した例です。
>>> eval('a', {'a': 20}, {'a': 10})
10
ローカル名前空間が優先して参照されることが分かります。
exec とは
exec
は第1引数を文として実行します。次は簡単な例です。
>>> exec('a = 1 + 2')
ここでは代入が実行されるだけで、評価した値は返らないため結果は出力されません。exec
の戻り値は常に None
になります。
>>> exec('a = 1 + 2') is None
True
ただし、変数 a
に評価した値が代入されているため、例えば print
によって内容を確認できます。
>>> exec('print(a)')
3
第1引数には eval
同様に文字列だけでなく、例えばバイトコードを指定することが出来ます。
exec の第2引数、第3引数
exec
も第2引数、第3引数でそれぞれグローバル、ローカルな名前空間を指定することが可能です。
次はローカル名前空間として a
に 10
を指定して実行した例です。
>>> exec('print(a)', {}, {'a': 10})
10
次はローカルな名前空間として空のディクショナリを渡した例です。
>>> a = {}
>>> exec('b = 3', {}, a)
>>> a
{'b': 3}
代入した値が参照できることが分かります。これに対して eval
は式の評価なので、名前空間の値を変更する事ができません。
組み込み参照の制限
exec
によってグローバル名前空間を変更または参照される事を制限したい事があります。例えば、ユーザから受け取った入力を実行する場合です。
この場合、第2引数に __builtins__
ディクショナリを指定する事で、実行時の組み込みを制限できます。
>>> exec('import os;os.system("echo danger!")', {}, {})
danger!
>>> exec('import os;os.system("echo danger!")', {'__builtins__':None}, {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
ImportError: __import__ not found
この例では __builtins__
ディクショナリに None
を指定しているので import os
実行時の呼び出しで __import__
が見つからずにエラーになっています。
eval と exec の違い
簡単にまとめると次のようになります。
eval | exec | |
---|---|---|
第1引数をどうするか | 評価 | 実行 |
第1引数 | 式 | 文 |
戻り値 | 評価の結果 | None |