今回は、文字列の中に記載されたメソッドや関数を Python から実行する方法です。
exec、eval、 literal_eval 、getattr あたりを使うことで実現可能なのですが、呼び出し側(文字列を関数として呼び出す側)と呼び出された側(文字列に記述した式)での値の受け渡しなどは、他のサイトではあまり触れられていないので、この記事ではその点を中心に解説したいと思います。
「文字列をプログラムとして実行する」とは
そもそも「文字列をプログラムとして実行する」というのは、例えば文字列として "myfunc(100)" という文字列が与えられた場合、「 myfunc(100) を実行する」と解釈して、myfunc(100)を実行するという事になります。
プログラムと言っても実際には関数に限ったものではなく、文字列をPythonのプログラムと解釈させて実行することが可能です。
文字列をプログラムとして実行する exec 関数
exec 関数は特になにもインポートせずとも使えるもので、引数で渡された文字列をpythonプログラムとして解釈して実行します。
exec(文字列)
例えば、GUIを持つ画面から入力されたpythonプログラムや、ファイルに保存された python プログラムを読み込んで実行する時に使います。
1 2 3 4 5 6 7 8 |
#プログラムを記述した文字列を用意 lines='def hoge(val):\n' lines+=' print(val * 2)\n' lines+='hoge(100)\n' #文字列の中身をプログラムとして実行 exec(lines) |
exec で呼び出されたプログラム文字列の中で、クラスや変数が生成された場合、呼び出し終わった後も呼び出した側から使えるようになります。
1つの文を実行して結果を返すeval関数
eval は exec と異なり、1つの文(ステートメント)のみの実行が可能です。
eval(文字列)
また、文を実行した際の結果は eval の戻り値として呼び出し元に返されます。
1 2 3 4 |
rate = 0.1 line = "100 * rate" data = eval(line) print(data) |
呼び出し側で既に定義されている関数やメソッドを実行したり、変数を参照することは可能ですが、変数への代入は出来ません。
eval を使って任意の関数や計算式の結果を受取る以外に、文字列でリストや辞書のデータ形式を登録しておき、eval を使ってリストや辞書のデータに変換するという用途にも使えます。
1 2 3 4 5 6 7 8 9 |
#リストの文字列からリストを作成 line = "[1,2,3,4,5"] data = eval(line) #リストが返される print(data) #辞書の文字列から辞書を作成 line = "{'item1:10','item2:20'}" data = eval(line) #辞書が返される print(data) |
文字列からリストや辞書を生成して返す literal_eval 関数
literal_eval は計算式や関数を実行するのではなく、文字列からリストや辞書などの値を取り出すための関数です。
literal_eval(文字列)
尚、literal_eval を使う場合は、import ast が必要です。
1 |
import ast |
literal_eval は、引数として渡される文字列は数値、リストや辞書、タプルなど値を返す内容に限られ、数式や関数、変数が含まれるとエラーになることでセキュリティの担保を図っています。
1 2 3 4 |
import ast line = '{"val1":5,"val2":10}' data= ast.literal_eval(line) print(data) |
literal_eval と 同じことは eval でも可能ですが、eval は計算式や関数呼び出しも可能であるがゆえ、セキュリティのリスクもあります。
例えば、あるファイルに掛かれている文を随時実行する場合、第三者によってそのファイルが改ざんされ、意図しない処理が実行される可能性があります。
このリスクを回避するため、計算式や関数を呼び出すことができない literal_eval が用意されています。
メソッド名の文字列でクラスのメソッドを呼ぶ getattr 関数
文字列でメソッド名を指定して、そのクラスにあるメソッドを呼び出す関数が gettattrです。
厳密には、指定したオブジェクトの属性を返す関数です。
オブジェクトとは、クラス、クラスのインスタンス、関数のことで、属性とはメソッド、クラス変数、インスタンス変数を指します。
getattr(オブジェクト、属性名、[default])
指定された属性名が存在しない場合はエラーになりますが、第3引数の default を設定しておくと、default の値が返されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#呼び出したいメソッドが含まれているクラス class MyClass(): def __init__(self): self.data = 100 def func(self,val1,val2=10,val3=999): print('val1={0} val2={1} val3={2}'.format(val1,val2,val3)) #クラスのインスタンスを生成 mc = MyClass() #funcという名前の属性(メソッド)を取得 myfunc = getattr(mc, 'func') #メソッドの実行(第一引数に50をセットし、あとの引数は省略) myfunc(50) |
サンプルソースはgetattrの値を一旦 myfunc という変数に受けてから実行していますが、以下の様に直接実行することも可能です。
1 2 |
#func メソッドの第一引数に50をセットして呼び出し getattr(mc, 'func')(50) |
getattr と literal_eval を組み合わせた使い方
例えば、関数と引数の一覧をテキストファイルに記述しておき、順次実行していきたい場合を考えます。
exec 関数を使うという方法が思い浮かびますが、テキストファイルを改ざんされてしまうセキュリティリスクを考えるなら、exec関数はあまり良い方法とは言えません。
そんな時、getattr と literal_eval を組み合わせましょう。
getattr は 対象となるインスタンスに含まれているメソッドや属性にしかアクセスできませんし、literal_eval は任意のメソッドや数式が実行できないため、この2つを組み合わせて使うことでセキュリティリスクをぐっと下げることが可能です。
以下がそのサンプルソースになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#呼び出したいメソッドが含まれているクラス class MyClass: def func(self,val1,val2=10,val3=999): return val1 + val2 + val3 import ast #引数の値とメソッド名を格納した変数 arg1 = '{"val1":5,"val2":10}' arg2 = 'func' #引数からメソッド名と引数の値を取り出して実行 mc = MyClass() dic = ast.literal_eval(arg1) getattr(mc, arg2)(**dic) |
この例では、以下のことを行っています。
- arg1 に代入された辞書形式の文字列を literal_eval で辞書の値に変換
- arg2 に登録されたメソッド名を getattr で取得し、引数に辞書を渡して実行
メソッド(又は関数)の引数は、あらかじめ辞書にセットしておき、まとめて引数として渡すことが可能なので、 getattr(mc, arg2)(**dic) という書き方をしています。
まとめ
今回は、文字列から exec、eval、 literal_eval 、getattr を使ってプログラムを実行したり、計算式やメソッド、関数を実行する方法について解説しました。
通常はあまり使う用途は無いかもしれませんが、テキストファイルに記述されている内容を実行したり、何らかのUIを持つプログラムで画面から入力されたプログラムを実行する場合などによく使われています。
使い方はアイデア次第ですが、「画面から何らかのパラメータや処理手順を変更し、すぐに実行して結果を確認したい」場合に使っています。
例えば機械学習で予測モデルを作成する場合のトライ&エラー作業などです。
皆さんも是非ご活用ください。
コメント