Python遞迴遍歷巢狀資料:return 結束的是目前分支,不是整個 traverse()

加入好友
加入社群
Python遞迴遍歷巢狀資料:return 結束的是目前分支,不是整個 traverse() - 儲蓄保險王

在處理 JSON 或設定檔時,資料通常不是只有一層,而是由:

  • dict
  • list
  • 純量,例如 strintboolNone

互相巢狀組成。

例如:

data = {
    "steps": [
        {
            "class_name": "LoginStep",
            "action": {
                "bypass": False
            }
        },
        {
            "class_name": "NetworkStep",
            "action": {
                "bypass": True
            },
            "children": [
                {
                    "class_name": "PingStep",
                    "action": {
                        "bypass": False
                    }
                }
            ]
        }
    ],
    "metadata": {
        "owner": "QA",
        "version": 2
    }
}

我們希望:

  1. 遍歷所有巢狀的 dict 與 list
  2. 找出具有 class_name 的節點;
  3. 依照 action["bypass"] 分成 Active 與 Bypass;
  4. 同時記錄節點路徑。

一、遞迴的核心概念

遞迴函式就是:

函式在處理目前資料時,再呼叫自己處理子資料。

基本形式如下:

def traverse(node):
    if node 是容器:
        for child in node:
            traverse(child)

遞迴一定要有兩個部分:

1. 繼續條件

遇到可以繼續展開的容器:

dict
list

就繼續呼叫 traverse()

2. 終止條件

遇到沒有子節點的資料:

str
int
float
bool
None

就停止目前這條路徑。

if not isinstance(node, (dict, list)):
    return

這個 return 就是遞迴的終止條件。


二、先看最簡單的遞迴版本

def simple_traverse(node):
    if isinstance(node, dict):
        for key, value in node.items():
            simple_traverse(value)

    elif isinstance(node, list):
        for item in node:
            simple_traverse(item)

    else:
        return

它的邏輯是:

Python遞迴遍歷巢狀資料:return 結束的是目前分支,不是整個 traverse() - 儲蓄保險王

其實純量的 else 也可以省略:

def simple_traverse(node):
    if isinstance(node, dict):
        for value in node.values():
            simple_traverse(value)

    elif isinstance(node, list):
        for item in node:
            simple_traverse(item)

因為當 node 不是 dict 也不是 list 時,函式會自然走到結尾,自動回傳:

return None

但在正式程式中,明確寫出:

if not isinstance(node, (dict, list)):
    return

會更容易表達:

純量是遞迴的終止點。


三、最重要的觀念:每次遞迴都是一次新的函式呼叫

假設程式執行:

traverse(data, "root")

遇到子節點時:

traverse(value, child_path)

這會建立另一個新的 traverse() 呼叫。

可以想成:

 1 次呼叫處理 root
     2 次呼叫處理 root | steps
         3 次呼叫處理 root | steps | 0

每一次呼叫都有自己的:

  • node
  • path
  • 執行位置
  • 回傳位置

所以在某一層執行:

return

只會結束:

目前這一次 traverse() 呼叫。

它會回到上一層,而不是直接消滅所有遞迴層級。


四、return 結束的是分支,不是整個遞迴

請看這個資料:

data = {
    "e": None,
    "a": {
        "b": [1, 2, {"c": 3}],
        "d": "hello"
    }
}

呼叫:

traverse(data, "root")

第一層是字典,因此程式執行:

for key, value in data.items():
    traverse(value, child_path)

先處理:

"e": None

等同於呼叫:

traverse(None, "root | e")

因為 None 是純量:

if not isinstance(None, (dict, list)):
    return

這個 return 的意思是:

結束 traverse(None, "root | e")
Python遞迴遍歷巢狀資料:return 結束的是目前分支,不是整個 traverse() - 儲蓄保險王
加入好友
加入社群
Python遞迴遍歷巢狀資料:return 結束的是目前分支,不是整個 traverse() - 儲蓄保險王

儲蓄保險王

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

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *