攝影或3C

Python Caller Name (呼叫者函數名) 教學:sys._getframe(1) vs inspect.currentframe().f_back # inspect.currentframe() 先拿到自己,再 .f_back 到 caller;等價於 sys._getframe(1)

這份文件示範如何在錯誤 helper 中「自動抓呼叫者函式名」,
並比較 `sys._getframe` 與 `inspect.currentframe`。

## 1) 問題背景

你有一個共用 helper,例如 `_raise_non_json_container_error(…)`,希望錯誤訊息自動顯示呼叫者名稱:

– `count_leaf_params()`

– `count_level_widths()`

而不是每次手動傳字串。

## 2) Call Stack 的 index 意義

兩種方案本質都在讀 call stack。

– `0`:目前函式自己

– `1`:直接呼叫者(caller)

– `2`:caller 的 caller

– `n`:更上層

如果索引超過 stack 深度,會出現例外(常見是 `ValueError`)。

## 3) 方案 A:sys._getframe

### 範例

import sys

def _raise_non_json_container_error(obj, func_name=None):
    if func_name is None:
        try:
            func_name = sys._getframe(1).f_code.co_name
        except (ValueError, AttributeError):
            func_name = "<unknown>"

    raise TypeError(
        f"{func_name}() only accepts JSON-compatible containers (dict/list). "
        f"Got non-JSON container: {type(obj).__name__}"
    )

### 優點

– 寫法短。

– 效能通常較好。

– 對「抓 caller 名稱」這種小用途很直接。

### 缺點

– `_getframe` 是 CPython 風格的低階 API,語意上比較偏內部細節。

– 可讀性對新手略差。

## 4) 方案 B:inspect.currentframe

### 範例(建議版,含 frame 清理)

import inspect

def _raise_non_json_container_error(obj, func_name=None):
    frame = None
    try:
        if func_name is None:
            # 先拿到目前這個 helper 自己 frame等同 sys._getframe(0))
            frame = inspect.currentframe()
            if frame is not None and frame.f_back is not None:
                # 往上一層到 caller等同 sys._getframe(1))
                func_name = frame.f_back.f_code.co_name
            else:
                func_name = "<unknown>"

        raise TypeError(
            f"{func_name}() only accepts JSON-compatible containers (dict/list). "
            f"Got non-JSON container: {type(obj).__name__}"
        )
    finally:
        # 避免 frame 參考循環
        del frame

### 優點

– 可讀性高,很多人直覺會先想到這個。

– 語意較「教科書化」。

### 缺點

– 稍微冗長。

– 效能通常略慢於 `sys._getframe`。

– 要注意 frame 參考循環(建議 `del frame`)。

## 5) 兩者對照總結

註:若兩邊都「只做一次性取值、不保存 frame 物件」,風險都很低。真正需要特別清理的是「把 frame 保存到變數/結構中」的情境。

## 6) 在你目前專案的建議

你的情境是「錯誤訊息 helper 自動帶 caller 名稱」,且路徑為熱路徑中的小工具函式:

– 如果你重視簡潔與實務效率:用 `sys._getframe(1)` 很合理。

– 如果你重視可讀性與教學一致性:用 `inspect.currentframe()` 也可以。

目前你已驗證 `sys` 寫法可正常輸出:

– `count_leaf_params()`

– `count_level_widths()`

在這個案子可直接採 `sys` 版本。

## 7) 最小可用測試片段

import sys

def _raise_non_json_container_error(obj, func_name=None):
    if func_name is None:
        func_name = sys._getframe(1).f_code.co_name
    raise TypeError(f"{func_name}() got {type(obj).__name__}")

def foo(x):
    if isinstance(x, (tuple, set)):
        _raise_non_json_container_error(x)

try:
    foo((1, 2))
except TypeError as e:
    print(e)
# foo() got tuple

## 8) 常見誤解

– 誤解:`1` 是固定魔法數字。

  – 事實:`1` 代表「上一層 caller」,不是只能用 1。

– 誤解:`inspect` 看起來多寫了 `.f_back`,所以邏輯不同。

    – 事實:邏輯相同。`inspect.currentframe()` 先拿到自己,再 `.f_back` 到 caller;等價於 `sys._getframe(1)`。

– 誤解:只能用 `inspect`。

  – 事實:`sys` 和 `inspect` 都能做到,取捨在可讀性與簡潔度。

– 誤解:一定要手動傳 `func_name`。

  – 事實:可設計成 optional,預設自動偵測,必要時手動覆蓋。

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

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡