# Python 自省 (Introspection) 實戰:教你如何讓程式碼「自我介紹」
你是否曾經在寫 Log 時,為了知道錯誤發生在哪個函式,而手動寫下這樣的程式碼?
def process_data():
try:
# ... 做一些事
pass
except Exception as e:
print("Error in process_data: ", e) # 手動要把函式名稱寫進去如果以後函式改名了,這個字串通常會忘記改,導致 Log 資訊誤導。其實,Python 作為一門具有強大 **自省 (Introspection)** 能力的語言,它完全知道「自己現在正在執行哪一行、在哪個函式裡」。
本文將帶你深入 Python 的幕後機制:**Function、Code Object 與 Frame** 的三角關係。
—
## 1. 用英文學程式:Inspection vs Introspection
在深入技術之前,我們先從日常生活來理解這兩個詞的差別,因為這正是 Python 設計這些功能的初衷。
### Inspection (視察 / 檢查)
* **字根拆解**:`In-` (進入、往裡) + `Spect` (看)。
* **關鍵差別**:`In-` 也是「裡面」的意思。但 inspection 通常指**由外部的人**「深入查看」另一個物體。
* **生活情境**:海關檢查行李 (Look into the bag)、品管員檢查產品。視角是「外 -> 內」。
### Introspection (內省 / 自省)
* **字根拆解**:`Intro-` (向內、在…之中) + `Spect` (看)。
* **關鍵差別**:`Intro-` 特指「朝向自我內在」。這不是檢查別人,而是檢查自己。
* **生活情境**:吾日三省吾身。視角是「內 -> 內」。
### Python 中的意義
當我們說 Python 支援 **Introspection** 時,是指賦予了程式碼一種**自我意識**。
正在執行的函式可以突然停下來問:「我是誰 (func name)?」、「是誰呼叫了我 (frame)?」、「我那一張靜態樂譜 (Code Object) 長什麼樣子?」。
這也是為什麼 Python 的標準模組雖然用 `inspect` (檢查) 命名,但我們探討的主題精確來說是 **Introspection** (自省)。
## 2. 核心三角關係:Function vs Code vs Frame
要理解 Python 怎麼執行程式的,可以用「演奏音樂」來比喻:
1. **Code Object (樂譜)**:
* 這是**靜態**的。
* Python 程式碼編譯後的 Bytecode、變數名稱列表、常數表都存在這裡。
* 它不知道「變數 x 現在等於多少」,只知道「這裡有個變數叫做 x」。
* 屬性特徵:以 `co_` 開頭,如 `co_name`, `co_varnames`。
2. **Function Object (樂手)**:
* 這是**靜態與動態的橋樑**。
* 它把「樂譜 (Code Object)」跟「環境 (Global 變數、Default 參數)」綁在一起。
3. **Frame Object (現場演奏)**:
* 這是**動態**的。
* 當你**呼叫**函式時,Python 會建立一個 Frame。
* 它記錄了「現在演奏到哪一行 (`f_lineno`)」、「現在 x 的值是多少 (`f_locals`)」。
* 函式結束,Frame 通常就會消失。
—
## 3. 實戰:我現在在哪裡?
讓我們看看如何動態取得當前的函式名稱。
### 方法 A:使用 `inspect` 模組 (推薦 / 標準做法)
import inspect
def who_am_i():
# 1. 取得當下的 Frame
frame = inspect.currentframe()
# 2. 從 Frame 裡面拿出 Code Object
code = frame.f_code
# 3. 從 Code Object 拿出函式名稱
func_name = code.co_name
# [實戰技巧] 資深工程師通常會直接寫成一行:
# func_name = inspect.currentframe().f_code.co_name
print(f"Hello, I am function: {func_name}")
who_am_i()
# 輸出: Hello, I am function: who_am_i
### 方法 B:使用 `sys` 模組 (資深工程師愛用 / 效能更好)
雖然 `inspect` 是標準介面,但你會發現很多資深工程師或效能敏感的專案 (如 Log 系統),更喜歡用 `sys`。
理由很簡單:
1. **更短**:`sys._getframe().f_code.co_name` 一行搞定。
2. **更快**:`sys` 是內建模組,比 `inspect` 輕量非常多。
3. **更常用**:因為 `sys.argv`, `sys.path` 太常用,
導致大家習慣直接用它,不想為了一個功能多 import 龐大的 `inspect`。
import sys
def debug_error():
try:
1 / 0
except ZeroDivisionError:
# _getframe(0) 代表拿那一層的 frame,0是自己,1是呼叫者
func_name = sys._getframe().f_code.co_name
print(f"[Error] Failed in {func_name}")
*注意:雖然 `_getframe` 前面有底線代表內部實作 (CPython),但因為太好用且太普遍,幾乎所有 Python 實作都會支援它。*
*(**PyPy** 是一個追求執行速度的 Python Alternative 實作,內建 JIT 讓程式跑得比標準 CPython 快很多)*
—
## 4. Python 底線命名學:為什麼要到底線?
剛才提到的 `_getframe` 以及 BeautifulSoup 的 `class_`,其實代表了 Python 社群兩種截然不同的命名慣例:
### A. 前單底線 (`_variable`):請勿打擾 (Internal Use)
* **例子**:`sys._getframe`、`my_object._private_method`。
* **意義**:「這是我的內部實作細節,雖然沒有強制禁止你用,後果自負。」
* **行為**:`from module import *` 會自動忽略開頭有底線的變數。
### B. 後單底線 (`variable_`):為了避嫌 (Avoid Conflicts)
* **例子**:BeautifulSoup 的 `find_all(class_=”highlight”)`。
* **意義**:原本想用的字是 Python 的**保留字** (Reserved Keyword),只好加個尾巴避開。
* **情境**:`class` 是 Python 定義類別的關鍵字,不能當參數名,所以 BeautifulSoup 團隊只好改用 `class_`。同理還有 `id_`、`type_` 等。
### C. 前後雙底線 (`__init__`):魔術方法 (Magic Methods)
* **意義**:這是 Python 語言層級的特殊方法,通常由直譯器自動呼叫,不建議使用者自己發明新的。
—
## 5. 深度探索:Code Object 的秘密
我們常說 Python 函式也是物件,那它的「內臟」到底長怎樣?我們可以透過 `func.__code__` 來偷看。
def calculate(a, b=10):
temp = a + b
return temp
code = calculate.__code__
print("函式名稱:", code.co_name) # calculate
print("定義在檔案:", code.co_filename) # .../temp.py
print("定義起始行:", code.co_firstlineno) # 1
# 這在寫 Decorator 或分析工具時很有用
print("用到的變數名:", code.co_varnames) # ('a', 'b', 'temp')
print("參數數量:", code.co_argcount) # 2
這就是為什麼 IDE 可以告訴你函式需要幾個參數,或者 Linter 可以在執行前就知道你用了一個沒定義的變數——因為它們都讀取了 Code Object。
## 5. 易混淆點:定義行號 vs 執行行號
* **`code.co_firstlineno`**: 這是函式 **def** 那一行在檔案中的位置。它是永遠不變的。
* **`frame.f_lineno`**: 這是程式 **目前跑到** 哪一行。它是隨著執行不斷變動的。
import inspect
def line_tracker():
f = inspect.currentframe()
print(f"Def line: {f.f_code.co_firstlineno}, Current line: {f.f_lineno}")
print(f"Def line: {f.f_code.co_firstlineno}, Current line: {f.f_lineno}")
line_tracker()**輸出範例:**

可以看到 Def line 固定不動,而 Current line 一直在往下跑。
## 總結
掌握 Python 的自省機制,能讓你從「寫程式的人」進階為「造工具的人」。
1. **Debug 更輕鬆**:不用再手寫函式名稱到 Log 裡,讓程式自己告訴你它在哪。
2. **理解 Decorator**:裝飾器之所以能保留原函式的資訊 (如 `wraps`),就是在操作這些屬性。
3. **黑魔法基礎**:很多強大的框架 (如 Pytest、SQLAlchemy) 都是依賴這些機制來實現「神奇」功能的。
下次當你 print 出 `<function xxx at 0x…>` 時,不妨試試 `dir(xxx.__code__)`,你會發現新世界!
推薦hahow線上學習python: https://igrape.net/30afN










近期留言