はじめに
Pythonの理解を深めるために、スタックフレームからどんな情報が得られるかを調査した。
思いの外大量の情報が得られてカオスになったので、目視できる程度の情報を雑にprint()
で表示してみた。
環境
$ python3 --version
Python 3.10.12
コード
コードが雑なので、説明も雑に。。。
スタックフレームのオブジェクトが持つインスタンス変数を表示する。
直接関係ない情報や大量に表示される情報(特に変数とバイトコード)もあるので、ある程度絞り込みを行っている。
中には無限再帰してしまうものもあったので、そういうものは適当に除外している。
import inspect
import json
import collections.abc
from typing import Any
exclude_preds = {
'frame': lambda name, value: (not name.startswith('f_')) or (name in ['f_back', 'f_builtins', 'f_globals', 'f_locals']),
'f_code': lambda name, value: (not name.startswith('co_')) or (name in ['co_varnames', 'co_cellvars', 'co_freevars', 'co_code', 'co_consts', 'co_names', 'co_lnotab', 'co_linetable'])
}
def default_exclude_pred(name: str, value: Any) -> bool:
return name.startswith('_')
def __collect_stackframe_details(name: str | None, value: Any) -> None | bool | int | float | str | list | dict:
if isinstance(value, None | bool | int | float | str):
return value
elif isinstance(value, list):
return [__collect_stackframe_details(None, entry) for entry in value]
else:
result: dict[str, Any] = {}
exclude_pred = exclude_preds[name] if name in exclude_preds else default_exclude_pred
for n, v in (value.items() if isinstance(value, collections.abc.Mapping) else inspect.getmembers(value)):
if exclude_pred(n, v):
continue
result[n] = __collect_stackframe_details(n, v)
return result
def collect_stackframe_details() -> list:
result = []
for stack_frame in inspect.stack():
result.append(__collect_stackframe_details(None, stack_frame))
return result
print(json.dumps(collect_stackframe_details(), indent=4))
実行結果
$ python3 stack_frame_details.py
[
{
"code_context": [
" for stack_frame in inspect.stack():\n"
],
"count": {},
"filename": "/home/jaybanuan/src/stack_frame_details/stack_frame_ditails.py",
"frame": {
"f_code": {
"co_argcount": 0,
"co_filename": "/home/jaybanuan/src/stack_frame_details/stack_frame_ditails.py",
"co_firstlineno": 36,
"co_flags": 67,
"co_kwonlyargcount": 0,
"co_lines": {},
"co_name": "collect_stackframe_details",
"co_nlocals": 2,
"co_posonlyargcount": 0,
"co_stacksize": 6
},
"f_lasti": 26,
"f_lineno": 40,
"f_trace": null,
"f_trace_lines": true,
"f_trace_opcodes": false
},
"function": "collect_stackframe_details",
"index": 0,
"lineno": 39
},
{
"code_context": [
"print(json.dumps(collect_stackframe_details(), indent=4)) \n"
],
"count": {},
"filename": "/home/jaybanuan/src/stack_frame_details/stack_frame_ditails.py",
"frame": {
"f_code": {
"co_argcount": 0,
"co_filename": "/home/jaybanuan/src/stack_frame_details/stack_frame_ditails.py",
"co_firstlineno": 1,
"co_flags": 64,
"co_kwonlyargcount": 0,
"co_lines": {},
"co_name": "<module>",
"co_nlocals": 0,
"co_posonlyargcount": 0,
"co_stacksize": 7
},
"f_lasti": 148,
"f_lineno": 45,
"f_trace": null,
"f_trace_lines": true,
"f_trace_opcodes": false
},
"function": "<module>",
"index": 0,
"lineno": 45
}
]
実はパッケージの情報が欲しかった
スタックフレームの中身を確認したかったのは、各スタックフレームがどのパッケージ由来なのかを知りたかったのだが、直接的な情報を見つけられなかった。
手元では試してないが、すごく頑張って似たような情報を構築している例を見つけた。
stackoverflow.com
参考
github.com
docs.python.org