Pythonのリテラル表記だけを読み戻す
手元で作った"[1, 2, 3]"という文字列をリストへ戻したいなら、ast.literal_eval()が使える。文字列を任意のPythonプログラムとして実行するのではなく、リスト・辞書・文字列・数値などのリテラルとして読み取る関数である。ただし、不特定の入力を安全に実行するためのサンドボックスではない。信頼でき、サイズも小さいデータの読み戻しに用途を限る。
ここでは、自分で管理する小さな設定値を整数のリストとして取り込む。文字数、要素数、各要素の型、値の範囲を明示し、読み取れたというだけで使わない。入出力の形式を自由に選べる新しいシステムなら、他の言語とも交換しやすいJSONなどを先に検討した方がよい場合もある。
解析後に必要な型を確認する
example.py
from ast import literal_eval
def read_integer_list(text):
if not isinstance(text, str):
raise TypeError("text must be str")
if len(text) > 200:
raise ValueError("text is too long")
try:
value = literal_eval(text)
except (SyntaxError, ValueError) as error:
raise ValueError("not a supported literal") from error
if type(value) is not list or len(value) > 10:
raise ValueError("expected a list of at most 10 items")
if any(type(item) is not int or not -100 <= item <= 100 for item in value):
raise ValueError("items must be integers from -100 to 100")
return value
assert read_integer_list("[1, -2, 3]") == [1, -2, 3]
assert read_integer_list("[]") == []
print(read_integer_list("[1, -2, 3]"))
for text in ("[True]", "(1, 2)", "[101]", "[1 + 2]", "list()", "[1,"):
try:
read_integer_list(text)
except ValueError:
print(repr(text), "rejected")
else:
raise AssertionError("unexpected acceptance")
実行結果
[1, -2, 3]
'[True]' rejected
'(1, 2)' rejected
'[101]' rejected
'[1 + 2]' rejected
'list()' rejected
'[1,' rejected
読み取れる形式と、受け入れる形式は別
literal_eval()自体はリスト以外のリテラルも読み取れる。しかし、この関数の目的は整数リストなので、タプルや辞書が返ってきてもエラーにする。ここを曖昧にしておくと、後続処理がリストを期待している場所へ別の型が渡る。設定を使う入口で形を確認すれば、かなり後になって無関係な場所で失敗することを減らせる。
type(item) is intとしているのはboolを含めたくないためである。Pythonのboolはintの派生型なので、isinstance(True, int)はTrueになる。測定点番号としてTrueを受け入れてよいかは用途によるが、この例では整数表記だけに限定した。逆に数値クラスの派生型も許すAPIなら、この厳密な型判定は強すぎる。入力形式に合わせて選ぶ。
文字数制限は解析を始める前、要素数と範囲の制限は解析後に行っている。巨大な文字列を先に解析してから長さを調べても、解析中の負荷は抑えられないためである。ただし200文字という値は例の小さな設定に合わせた制約であり、これを付ければどんな相手からの入力も安全になる、という意味ではない。
「コードを実行しない」だけでは十分でない
公式ドキュメントは、複雑な入力によってメモリやCスタックを消費し、プロセスへ影響する可能性を注意している。literal_eval()は名前を解決して自由な関数を呼び出すための機能ではないが、資源消費を無制限に防ぐ機能もない。Webフォームや外部サービスから来た任意の値へ無条件に適用しない。信頼境界がある場合は形式の選定、入力の上限、必要に応じた処理の分離を含めて考える。
例ではlist()や[1 + 2]を不正な入力として確認している。関数呼び出しや計算式を設定として受け入れる目的には使わない。一方で、空の集合表記など一部の特別な構文はPythonの版によって対応が異なるため、「すべての呼び出し表記が同じ扱い」と一般化せず、実際に必要な形式だけをテストする。
エラーを捕まえて元の文字列をそのまま値として返すのも避けたい。正常時にはlist、失敗時にはstrになると呼び出し側が分岐を忘れやすい。ここではValueErrorへまとめ、元の原因をraise fromで残した。入力のどの項目が間違っているかは、関数を呼ぶ側で項目名と一緒に伝えるとよい。
保存形式を新しく決める場合
Pythonの表記とJSONは同じではない。JSONのtrueやnullはPythonのTrueやNoneとは違い、引用符などにも規則がある。拡張子や見た目から推測して片方を試すのではなく、保存側と読み込み側で形式を決める。過去データを移行する場合は、元の値を残した状態で変換し、件数と型を検査してから新形式へ切り替える。
実行環境と関連情報
掲載コードはLinux上のCPython 3.12.14で実行した。OS固有のファイル操作や対話環境の違いは、本文に記した条件に従って扱う。
関連:JSONで保存できない値をどう扱う?日時・数値・日本語の受け渡し / pytestのparametrizeで境界値と入力パターンをまとめて検証する
- Python公式ドキュメント(2026年10月2日参照)
