Pythonのフレームオブジェクト。 それでできることとできないこと(生産および別の適切な場所)

Habré上のPythonについては、たくさんの良い記事がありました。 実装の機能について、および他の主流言語では利用できない適用機能について。 しかし、Habréでもロシア語を話すインターネット一般でも公開されていない重要なトピックが1つあることを知って(正しくない場合は正しい)驚きました。 この記事では、スタックフレームなどに焦点を当てます。 ほとんどの場合、彼女は何も言わないか、または経験豊富なPython開発者にとってほとんど新しいものがないという最後の点を考慮に入れるかもしれませんが、初心者には役立ちます(そして有害かもしれませんが、すべての例は以下です)。



読みやすいように記事を書き、replを並行して開き、 そこに実験コード思いやりなくコピーしてました 。 したがって、可能であれば、ほとんどの例は「単一行インタープリター」の形式です。



Tracebackがオブジェクトでもあることに気づくことで、少し離れたところから始めて、スタックフレームの場所を見つけてポイントに到達します。



>>> import requests >>> requests.get(42) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3/dist-packages/requests/api.py", line 55, in get return request('get', url, **kwargs) File "/usr/lib/python3/dist-packages/requests/api.py", line 44, in request return session.request(method=method, url=url, **kwargs) File "/usr/lib/python3/dist-packages/requests/sessions.py", line 421, in request prep = self.prepare_request(req) File "/usr/lib/python3/dist-packages/requests/sessions.py", line 359, in prepare_request hooks=merge_hooks(request.hooks, self.hooks), File "/usr/lib/python3/dist-packages/requests/models.py", line 287, in prepare self.prepare_url(url, params) File "/usr/lib/python3/dist-packages/requests/models.py", line 338, in prepare_url "Perhaps you meant http://{0}?".format(url)) requests.exceptions.MissingSchema: Invalid URL '42': No schema supplied. Perhaps you meant http://42?
      
      







上記のトレースバックには、さまざまな関数の呼び出しのチェーンが含まれています。



 >>> import sys >>> sys.last_traceback <traceback object at 0x7f8c37378608>
      
      







 >>> dir(sys.last_traceback) ['tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next']
      
      







したがって、上記の最後の未処理の例外を含むオブジェクトが表示されます。 デバッガでそれを調べて、何が起こったかを確認できます。 Tracebackモジュールを使用することもできます。 これについては特に説明しませんが、やや話題になります。



tb_frame属性に興味があります。 これは実際にはスタックフレームです。 その本質は、Cの本質と基本的には異なりません。関数が呼び出されると、その引数はスタックに置かれ、その後で実行されます。



ただし、Cとは2つの基本的な違いがあります。



第一に、Pythonはインタープリター言語であるという事実のために、スタックフレームは明示的にその中に格納され、第二に、Pythonはオブジェクト指向であるという事実のために、オブジェクトでもあります。



実際、これはPythonのイントロスペクションの主なソースです。inspectモジュールは部分的に単なるラッパーです。 (およびsysからの他の関数で部分的に)。



オブジェクトのすべての属性の詳細な説明は他の場所で読むことができるので、アプリケーションの例に移りましょう。



0)スタックフレームへのリンクを取得する方法



もちろん、トレースバックから取得できます。 しかし、幸いなことに、例外をスローしてトレースバックを監視するよりも多くの方法があります。



 >>> sys._getframe() <frame object at 0x7fda9bffa388>
      
      





呼び出しスタックから現在のオブジェクトを返します。 スタック内でオブジェクトをより高くするためのパラメータとして深さを渡すこともできます。 ただし、これはf_back属性を使用して行うこともできます。



残念ながら、replを開始した場合、スタックの最上位にいるため、sys._getframe()を呼び出すと、F_backはNoneを返し、sys._getframe(1)は例外をスローします。



 >>> sys._getframe().f_back >>> sys._getframe(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: call stack is not deep enough
      
      





ただし、ラムダ関数はこの問題を解決します。



 >>> (lambda: sys._getframe().f_back == sys._getframe(1))() True   (   ) >>> (lambda: sys._getframe().f_back is sys._getframe(1))() True
      
      





sys._current_frames()はまだありますが、マルチスレッドの場合には意味があります。 これ以上は言及しませんが、全体像を説明します。



 >>> import threading >>> sys._current_frames() {140576946280256: <frame object at 0x7fda9bffa6c8>} >>> threading.Thread(target=lambda: print(sys._current_frames())).start() {140576883275520: <frame object at 0x7fda9b337048>, 140576946280256: <frame object at 0x228fbc8>}
      
      





さて、これらの関数のドキュメントを引用する時が来ました:

CPython実装の詳細:この関数は、内部および特殊な目的にのみ使用する必要があります。 Pythonのすべての実装に存在するとは限りません。




したがって、利己的な目的で使用することはほとんどありません。



1)関数を呼び出した人を決定する方法



同様のタスクは、特に多くのサードパーティの混乱したフレームワークが使用されている場合、デバッグ中に簡単に発生します。 ただし、黒魔術の熟練者は、このようなトリックとそのバリエーションを使用して、無害なものをはるかに少なくすることができます。



最後のサブパラグラフの後、これを行う方法は一般的に明確になるはずです。 実際、関数が呼び出されたフレームを取得するのは非常に簡単です:sys._getframe(1)。 問題は、フレームから必要な情報を抽出する方法です。



たとえば、次のように:



 >>> threading.Thread(target=lambda: print(sys._getframe(1).f_code.co_name)).run() run
      
      





コードオブジェクトの説明は、この記事の範囲外です。 ただし、フレームオブジェクトには属性f_codeがあります。これはコードオブジェクトであり、その属性の中には、対応するコードオブジェクトのメソッドの名前もあります。



ところで、検査の場合、実際には同じことが起こりますが、きれいに見えます。



 >>> threading.Thread(target=lambda: print(inspect.stack()[1][3])).run() run
      
      





読者はメソッドを悪用する機会を見つけるでしょう。

そのようなアイデアを推進する別の例:



 >>> threading.Thread(target=lambda: print('called from', sys._getframe(1).f_globals['__name__'])).run() called from threading
      
      





これで、モジュールの名前を受け取ったので、その名前でロードされたモジュールのリストでモジュールを見つけて、そこから何かを取り出すことができます。 そして、クラス階層はそのような犠牲から救うことはありません(しかし、良いものはおそらくこれをしたいという欲求から救うでしょう)



2)フレームのローカルを変更します



このサブパラグラフの情報はそれほど新しいものではなく、高度なパイニストに知られるべきですが、それでもなおそうです。



この時点で、シングルラインプレイヤーから離れ、本格的な例を作成する必要があります。



次のコードがあるとします:



 import sys class Example(object): def __init__(self): pass def check_noosphere_connection(): print('OK') def proof(example): a = 2 b = 2 example.check_noosphere_connection() print('2 + 2 = {}'.format(str(a + b))) def broken_evil_force(): def corrupted_noosphere(pseudo_self): print('OK') frame = sys._getframe() frame.f_back.f_locals['a'] = 3 FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere}) proof(FakeExample())
      
      







 % python habr_example.py OK 2 + 2 = 4
      
      





残念ながら、あなたは地元の人々を変えることはできません。 より正確には、可能ですが、この結果はグローバルに保存されません。



このトピックへの2つのリンクがあります。1つはバイトコードを変更することで目的の結果を達成し、もう1つはソースコード自体を変更してコンパイルします。



code.activestate.com/recipes/577283-decorator-to-expose-local-variables-of-a-function-

stackoverflow.com/a/4257352



ただし、方法があります! pydev.blogspot.ru/2014/02/changing-locals-of-frame-frameflocals.html



cctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame)、ctypes.c_int(0))を呼び出すと、ローカルへの変更を保存できます。



 import sys import ctypes class Example(object): def __init__(self): pass def check_noosphere_connection(): print('OK') def proof(example): a = 2 b = 2 example.check_noosphere_connection() print('2 + 2 = {}'.format(str(a + b))) def broken_evil_force(): def corrupted_noosphere(pseudo_self): print('OK') frame = sys._getframe() frame.f_back.f_locals['a'] = 3 FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere}) proof(FakeExample()) def evil_force(): def corrupted_noosphere(pseudo_self): print('OK') frame = sys._getframe() frame.f_back.f_locals['a'] = 3 ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame.f_back), ctypes.c_int(0)) FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere}) proof(FakeExample()) if __name__ == '__main__': broken_evil_force() evil_force()
      
      





実行して取得:



 % python habr_example.py OK 2 + 2 = 4 OK 2 + 2 = 5
      
      





おそらくそれだけです。 追記しない限り、トレースバックの場合、フレームに沿って上に歩くだけでなく下に移動することもできますが、これはすでに明らかであるはずです。



フレームの使用の興味深い例だけでなく、コメントやコメントを聞いてうれしいです。



便利なリンク(一部は記事にありました)



1) docs.python.org/3.4/library/sys.html

2) docs.python.org/3/library/inspect.html

3) code.activestate.com/recipes/577283-decorator-to-expose-local-variables-of-a-function-

4) stackoverflow.com/questions/4214936/how-can-i-get-the-values-the-of-the-locals-of-of-a-function-after-it-has-been-executed/4257352#4257352

5) pydev.blogspot.ru/2014/02/changing-locals-of-frame-frameflocals.html



All Articles