Pythonには、文字列として表現されたコード(式)を、実際のプログラムとして解釈・実行するための組み込み関数が存在します。
この機能は非常に強力であり、ユーザー入力に応じた計算や、動的な条件判定などに利用できます。しかし、その強力さゆえに、使い方を誤ると深刻なセキュリティホール(脆弱性)を生む危険性があります。
この記事では、文字列をコードとして実行する関数の基本的な使い方、変数との連携、そして最も重要なセキュリティ上の注意点と代替案について解説します。
文字列を式として実行する関数
Pythonには、引数として渡された「文字列」を Python の「式(Expression)」として解析し、実行する関数があります。
構文:
result = 実行関数("実行したい式")
注意点: この関数で実行できるのは「式(値を返すもの)」のみです。代入文(a = 10)や import 文、def 文などの「文(Statement)」は実行できません。文を実行したい場合は別の関数(execなど)を使用しますが、本記事では「式の評価」に絞って解説します。
具体的な使用例:動的な面積計算
例えば、設定ファイルやユーザー入力から、面積を計算するための数式が文字列として渡されるケースを想定します。
# 文字列として定義された計算式
expression = "10 * 20 + 5"
# 文字列を計算式として評価
calculated_area = eval(expression)
print(f"計算式: {expression}")
print(f"結果: {calculated_area}")
print(f"結果の型: {type(calculated_area)}")
実行結果:
計算式: 10 * 20 + 5
結果: 205
結果の型: <class 'int'>
文字列 "10 * 20 + 5" が、Pythonのコード 10 * 20 + 5 として解釈され、計算結果である 205 が返されました。
変数を含んだ式の評価
この関数は、現在のスコープ(その場所で有効な変数)を参照して式を評価できます。これにより、あらかじめ定義された変数を使った動的な計算が可能になります。
具体的な使用例:パラメータを用いた計算
# 計算に使用する変数
width = 50
height = 80
offset = 10
# 変数名を含んだ文字列の式
formula = "(width + height) * offset"
# 現在の変数の値を使って評価される
result = eval(formula)
print(f"式: {formula}")
print(f"計算結果: {result}")
実行結果:
式: (width + height) * offset
計算結果: 1300
このように、文字列の中に変数名が含まれていても、実行時にその変数の値が参照され、正しく計算が行われます。
【重要】セキュリティリスクと使用上の注意
この機能は「任意のコードを実行できる」ものです。これは、外部からの入力(ユーザー入力や通信データ)をそのまま渡すと、悪意のあるコードを実行されるリスクがあることを意味します。
危険なコードの例
もしユーザーが、計算式の代わりにシステムを操作するコードを入力した場合、何が起きるか見てみます。
import os
# 悪意のあるユーザーが入力した文字列(例)
# osモジュールを使ってディレクトリ一覧を表示するコード
malicious_input = "__import__('os').getcwd()"
# 実行してしまう
current_dir = eval(malicious_input)
print(f"現在のディレクトリ: {current_dir}")
この例ではカレントディレクトリの取得で済んでいますが、__import__('os').system('rm -rf /') のようなコマンドが含まれていれば、システムのファイルが削除される恐れがあります。
原則: 信頼できないソースからの入力をそのまま実行してはいけません。
安全な代替案:ast.literal_eval
単に「文字列形式のデータ(リストや辞書など)」を Python オブジェクトに変換したいだけであれば、標準ライブラリ ast モジュールの literal_eval() を使用するべきです。
literal_eval() は、Pythonのリテラル構造(文字列、数値、タプル、リスト、辞書、ブール値、None)のみを安全に評価し、関数呼び出しや複雑な演算は許可しません。
import ast
# 文字列形式の辞書データ
data_str = "{'name': 'Tanaka', 'score': 95}"
try:
# 安全に辞書に変換
data_dict = ast.literal_eval(data_str)
print(f"変換後のデータ: {data_dict}")
print(f"型: {type(data_dict)}")
# 危険なコードはエラーになる
# ast.literal_eval("__import__('os')") # ValueError: malformed node or string
except (ValueError, SyntaxError):
print("データの変換に失敗しました。")
実行結果:
変換後のデータ: {'name': 'Tanaka', 'score': 95}
型: <class 'dict'>
まとめ
安全なデータ変換には ast.literal_eval() の使用を推奨します。
文字列をPythonの式として評価・実行する関数があります。
文字列内の数式計算や、変数を使った動的な評価が可能です。
極めて高いセキュリティリスクがあるため、外部入力を扱う場合には使用を避けるべきです。
