在動態語言 Python 中,自省(introspection)讓你於執行期檢視程式自身:你可以知道「哪個函式正在跑」、「它最初定義在哪一行」、「當前這次呼叫的局部變數是什麼」。這種能力是除錯、產生文件、測試框架、追蹤呼叫、建 plugin/registry 系統的重要基礎。
然而初學者常把「函式」當成一個單一黑盒;實際上,一次函式呼叫至少牽涉三個層級:
Function 物件:承載可呼叫介面與 code。
Code Object:編譯後的不可變結構(名稱、起始行、參數佈局、常量池等)。
Frame:本次呼叫的執行環境(當前行號、locals、上一層 f_back)。
本精簡版示例只保留最能打開視角的三個主題:
(a) 如何取得與觀察 frame / code。
(b) 行號差異:定義起始行 (co_firstlineno) vs 目前執行行 (f_lineno)。
(c) 快速列印 code object 常用屬性(變數、常量、自由變數)。
若你需要更進一步(簽名綁定、原始碼擷取、呼叫堆疊掃描、decorator metadata、閉包結構、生成器/協程判斷),請參考完整版範例;本檔案旨在降低認知負荷,先讓「frame / code / runtime」的 mental model 成形。
Section 1. Function 物件 (def
產出) vs Code Object (func.__code__
) vs Frame:
透過 `inspect.currentframe()` 取得 frame,解析 `frame.f_code`, 比對 `co_name / co_filename / co_firstlineno` 與執行行號差異。
Section 2. 行號差異:
`frame.f_lineno` (目前執行位置) vs `co_firstlineno` (定義起始行)。
Section 3. 快速列印常見 code object 屬性:
封裝 `show_code_attrs`,便於觀察 參數數量、變數名稱、常量池、自由變數等核心結構。
"""my_demo_less.py
精簡版 Python introspection (自省) 示範
=================================================
此檔為完整版教學的「精簡 / 入門」版本,僅保留最核心三個觀念:
Section 1. Function 物件 vs Code Object vs Frame:
透過 `inspect.currentframe()` 取得 frame,解析 `frame.f_code`,
比對 `co_name / co_filename / co_firstlineno` 與執行行號差異。
Section 2. 行號差異:`frame.f_lineno` (目前執行位置) vs `co_firstlineno` (定義起始行)。
Section 3. 快速列印常見 code object 屬性:封裝 `show_code_attrs`,便於觀察
參數數量、變數名稱、常量池、自由變數等核心結構。
(若需簽名解析、原始碼抓取、呼叫堆疊、decorator metadata、閉包深入、
generator / coroutine 判斷等進階主題,請參考完整版 `my_demo.py`。)
"""
from __future__ import annotations
import inspect
import textwrap
from functools import wraps
import types
print("="*70)
print("[Section 1] Function / Code Object / Frame 基本關係")
def demo(x):
"""分層展示:frame -> frame.f_code -> code object 屬性。
教學路徑:
(a) 取得目前執行中的 frame (inspect.currentframe)
(b) 檢視 frame 物件與其型別
(c) 取出 frame.f_code (對應函式的 code object)
(d) 檢視 code object 型別與核心屬性 (co_name / co_filename / co_firstlineno)
(e) 比較 co_firstlineno (定義起始行) 與 frame.f_lineno (目前執行行)
(f) 用 frame.f_code.co_name 從 globals 回推函式物件,避免硬寫函式名
重點:function 物件 (demo) 與一次呼叫的 frame 是不同層級;多次呼叫會產生多個 frame,但共用同一個 code object。
"""
frame = inspect.currentframe() # (a)
print("(a) current frame obtained")
print(" frame: ", frame) # (b)
print(" type(frame): ", type(frame))
code = frame.f_code # (c)
# 說明:
# frame.f_code 與 (此函式物件).__code__ 指向同一個 code object。
# 若此函式目前名稱為 demo,則 demo.__code__ is frame.f_code 為 True。
# 但直接硬寫 demo.__code__ 會綁定「符號名稱」,將來重構改名需同步修改;
# 利用 frame.f_code 取得 code object 則是“名稱無關”(name-agnostic),
# 可再透過 frame.f_code.co_name 動態回推 globals 內對應函式,而不依賴舊名稱。
# 若外部仍需要舊名稱相容,可以建立 alias,例如 new_name = demo。
print("(c) code = frame.f_code")
print(" code object: ", code)
print(" type(code): ", type(code)) # (d)
resolved_func = frame.f_globals.get(code.co_name) # (f)
print("(f) resolved_func via globals[co_name]:", resolved_func)
if resolved_func is not None:
print(" resolved_func.__code__ is code:", resolved_func.__code__ is code)
print("(d) code object core attributes")
print(" co_name: ", code.co_name)
print(" co_filename: ", code.co_filename)
print(" co_firstlineno:", code.co_firstlineno)
print("(e) runtime line compare")
print(" frame.f_lineno (current line):", frame.f_lineno)
print(" code.co_firstlineno (def line):", code.co_firstlineno)
return x * 2
result_demo = demo(1)
print("demo(1) =>", result_demo)
print("="*70)
print("[Section 2] frame.f_lineno vs co_firstlineno 差異")
def line_progress():
frame = inspect.currentframe()
print("起始 co_firstlineno:", frame.f_code.co_firstlineno)
# 下列列印行號會逐行變動
print("目前執行行號 a:", frame.f_lineno)
print("目前執行行號 b:", frame.f_lineno) # 仍是本行,解釋器求值完再遞增
# 插入一個子函式呼叫,觀察回來後行號
_inner()
print("回來後行號 c:", frame.f_lineno)
def _inner():
pass
line_progress()
print("="*70)
print("[Section 3] 快速列印常見 code object 屬性")
def show_code_attrs(fn, attrs=None):
"""列印函式 code 物件的重要屬性 (精簡版)。
attrs: 若提供自訂屬性名稱列表;否則使用預設精選集合。
"""
if attrs is None:
attrs = [
"co_name", "co_filename", "co_firstlineno",
"co_argcount", "co_posonlyargcount", "co_kwonlyargcount",
"co_varnames", "co_nlocals", "co_consts", "co_names",
"co_freevars", "co_cellvars"
]
code = fn.__code__
print(f"<code attrs of {fn.__name__}>:")
for a in attrs:
val = getattr(code, a)
# 過長內容截斷顯示 (例如 co_consts)
text = repr(val)
if len(text) > 120:
text = text[:117] + '...'
print(f" {a:>15}: {text}")
# 示範列印 demo 的 code 屬性
show_code_attrs(demo)
輸出:


推薦hahow線上學習python: https://igrape.net/30afN
近期留言