Python(4)関数の引数の変数名やそれに代入されているコードを取得

ラベル:

前の関連記事:Python(3)WSGI:ウェブページからshutdown()メソッドでWSGIサーバを停止させる


関数の引数にされた変数名やスクリプトでその中身に代入されたものをソースコードから得たいときがあります。Pythonではinspectモジュールを使うとそれができるようです。

inspectモジュールを使ってコード自身の情報を得る


from wsgiref.simple_server import make_server
import webbrowser
def app(environ, start_response):  # WSGIアプリ。evironは必須WSGI変数を含む辞書。
    start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])  # start_responseは2つの必須固定引数statusとresponse_headers(タプル)、他にexc_infoを含む。
    html = 'テスト'.encode()  # 出力するhtml。
    insp(html)
    return [html]  # htmlをバイト列に変換してWSGIサーバに渡す。
def main():
    host, port = "localhost", 8000  # サーバが受け付けるポート番号を設定。
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。
    url = "http://{}:{}".format(host, port)  # 出力先のurlを取得。
    webbrowser.open_new_tab(url)  # デフォルトのブラウザでurlを開く。
    httpd.handle_request()  # リクエストを1回だけ受け付けたらサーバを終了させる。
def insp(obj):
    pass
if __name__ == "__main__":  # ここから開始。
    main()
これはデフォルトブラウザに"テスト"と表示するプログラムです。

14行目に定義した関数insp()はここでは何もしていませんが、これからこの関数の引数objにいれらた変数名(6行目のhtml)やそれに代入されているもの(5行目の'テスト'.encode())を得たいと思います。

ソースコードから情報を得るにはinspectモジュールを使えばよいようです、、、

とっかかりをつかむのに試行錯誤しましたがinspect.currentframe()でフレームが取得できました。

「フレーム」とは何なのか、を解説しているものはみつけられませんでしたが、とりあえず使ってみると実行中の関数の情報が得られました。

inspect.currentframe()を実行した関数を呼び出している関数のフレームはf_backで呼び出せました。

さらにco_codeでそのフレームのコードオブジェクトが得られますのでフレームオブジェクトとコードオブジェクトから得られる役立ちそうな情報を出してみます。
from wsgiref.simple_server import make_server
import webbrowser
def app(environ, start_response):  # WSGIアプリ。evironは必須WSGI変数を含む辞書。
    start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])  # start_responseは2つの必須固定引数statusとresponse_headers(タプル)、他にexc_infoを含む。
    html = 'テスト'.encode()  # 出力するhtml。
    insp(html)
    return [html]  # htmlをバイト列に変換してWSGIサーバに渡す。
def main():
    host, port = "localhost", 8000  # サーバが受け付けるポート番号を設定。
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。
    url = "http://{}:{}".format(host, port)  # 出力先のurlを取得。
    webbrowser.open_new_tab(url)  # デフォルトのブラウザでurlを開く。
    httpd.handle_request()  # リクエストを1回だけ受け付けたらサーバを終了させる。
def insp(obj):  # これを呼び出したソースコードの情報を取得する。
    import inspect
    cur_frame = inspect.currentframe()  # この関数のフレームオブジェクトを取得。
    cur_frame_co = cur_frame.f_code  # この関数のコードオブジェクトを取得。
    print("このフレームのコードオブジェクトから得たこの関数名 = {}".format(cur_frame_co.co_name))
    # print("そのコードオブジェクトから得たこの関数の引数の数 = {}".format(cur_frame_co.co_argcount))
    bac_frame = cur_frame.f_back  # この関数の呼び出し元のフレームオブジェクトを取得。
    bac_frame_info = inspect.getframeinfo(bac_frame)  # フレームオブジェクトのフレームレコードを取得。
    # print("呼び出し元のフレームレコード(名前付タプル) = {}".format(bac_frame_info))
    # print("そのフレームレコードから得たファイル名 = {}".format(bac_frame_info.filename))
    # print("そのフレームレコードから得た行番号 = {}".format(bac_frame_info.lineno))  # bac_frame.f_linenoと同じ。
    # print("そのフレームレコードから得た関数名 = {}".format(bac_frame_info.function))
    print("そのフレームレコードから得たソースコードのリスト = {}".format(bac_frame_info.code_context))
    print("そのフレームから得た行番号 = {}".format(bac_frame.f_lineno))
    # print("そのフレームのローカル名前空間の辞書 = {}".format(bac_frame.f_locals))
    bac_frame_co = bac_frame.f_code  # この関数の呼び出し元のコードオブジェクトを取得。
    # print("呼び出し元のコードオブジェクトを生成したファイル名 = {}".format(bac_frame_co.co_filename))
    # print("そのコードオブジェクトから得たローカル変数名のタプル = {}".format(bac_frame_co.co_names))
    # print("そのコードオブジェクトから得た引数とローカル変数のタプル = {}".format(bac_frame_co.co_varnames))
    print("そのファイルのソースコードの先頭行 = {}".format(bac_frame_co.co_firstlineno))
if __name__ == "__main__":  # ここから開始。
    main()
使わなさそうな情報はコメントアウトしています。

この実行結果は以下になります。
このフレームのコードオブジェクトから得たこの関数名 = insp
そのフレームレコードから得たソースコードのリスト = ['    insp(html)\n']
そのフレームから得た行番号 = 6
そのファイルのソースコードの先頭行 = 3
ソースコードのリストは21行目のinspect.getframeinfo(frame, context=1)でcontextを増やすたびに呼び出し元の行を中心に上下のソースコードを取得してくれます。

引数の変数名はソースコードのリストから簡単に得られそうですが、その変数に代入されているコードを探すのはちょっと工夫しないといけません。
from wsgiref.simple_server import make_server
import webbrowser
def app(environ, start_response):  # WSGIアプリ。evironは必須WSGI変数を含む辞書。
    start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])  # start_responseは2つの必須固定引数statusとresponse_headers(タプル)、他にexc_infoを含む。
    html = 'テスト'.encode()  # 出力するhtml。
    insp(html)  # これ自身の引数を調べる関数。
    return [html]  # htmlをバイト列に変換してWSGIサーバに渡す。
def main():
    host, port = "localhost", 8000  # サーバが受け付けるポート番号を設定。
    httpd = make_server("", port, app)  # appへの接続を受け付けるWSGIサーバを生成。
    url = "http://{}:{}".format(host, port)  # 出力先のurlを取得。
    webbrowser.open_new_tab(url)  # デフォルトのブラウザでurlを開く。
    httpd.handle_request()  # リクエストを1回だけ受け付けたらサーバを終了させる。
def insp(obj):  # これを呼び出したソースコードの情報を取得する。
    import inspect, re
    bac_frame = inspect.currentframe().f_back  # この関数の呼び出し元のフレームオブジェクトを取得。
    bac_code = inspect.getframeinfo(bac_frame).code_context[0].replace(" ", "")  # 呼び出し元のソースコードのリストからstring.whitespace(' \t\n\r\v\f')を削除して1行を取得。
    pos = bac_code.find("#")  # コメントの開始位置を取得。
    if pos != -1: bac_code = bac_code[:pos]  # コメントがあるときはそれを削除。
    arg = re.search(r"\((.+)\)", bac_code).group(1)  # 呼び出し元関数の引数の変数名を取得。
    m = bac_frame.f_code.co_firstlineno  # 呼び出し元のコードオブジェクトからそのソースコードの先頭行番号を取得。
    n = bac_frame.f_lineno  # この関数を呼び出した呼び出し元の行番号を取得。
    lst_bac_codes = inspect.getframeinfo(bac_frame, 2*(n-m-1)).code_context  # 呼び出し元のソースコードを先頭行の次行から呼び出し元行まで入るように取得。
    flg = False  # 呼び出し元行にきたら立つフラグを作成。
    txt = "{}=".format(arg)  # 「引数=」となる文字列を取得。
    for i in reversed(lst_bac_codes):  # 取得したソースコードについて最下行から各行について。
        i = i.replace(" ", "")  # string.whitespace(' \t\n\r\v\f')を削除。
        pos = i.find("#")  # コメントの開始位置を取得。
        if pos != -1: i = i[:pos]  # コメントがあるときはそれを削除。
        if flg:  # フラグが立っているとき。
            if i.startswith(txt):  # 行が「引数=」で始まるとき。
                arg = i[len(txt):]  # 「引数=」の右辺を取得。
                break  # ループをでる。
        if i == bac_code: flg = True  # 呼び出し元行にきたらフラグを立てる。
    print(arg)  # 引数の内容を表示。
if __name__ == "__main__":  # ここから開始。
    main()
これを実行するとinsp()の引数に代入されているコード「'テスト'.encode()」が出力されます。

引数を定義した行がないときは引数そのものが出力されます。
    insp('テスト'.encode())  # これ自身の引数を調べる関数。
例えばこうすると引数にしたもの「'テスト'.encode()」が出力されます。

これで期待した動作をするものが得られましたが期待したより長いコードになってしまいました。

参考にしたサイト


28.12. inspect — 活動中のオブジェクトの情報を取得する — Python 3.3.3 ドキュメント
inspect.currentframe()を皮切りにソースコードの情報をいろいろ取得できます。

次の関連記事:Python(5)クロージャ(Closure)のお勉強

PR

0 件のコメント:

コメントを投稿