在處理資料(例如解析 PDF、爬蟲抓取的 JSON 等)時,
我們常常需要從一個包含許多
「字典」(Dictionary) 或「物件」(Object) 的陣列中,
挑出某個在特定條件下最大的元素。
在 PyMuPDF 的操作中,最經典的例子就是:
**從一行字被切碎的多個 `spans` 中,
選出文字最長的那一個作為該行的「主要字型」**。
## 情境:被切碎的一行字
假設我們解析出來的一條 `line` 裡面有 4 個 `span`,
它們被放在一個 list 裡:
spans = [
{"text": "He", "font": "Helvetica", "size": 11}, # 長度 2
{"text": "ll", "font": "Helvetica", "size": 11}, # 長度 2
{"text": "BOLD", "font": "Helvetica-Bold", "size": 11}, # 長度 4
{"text": "o world!", "font": "Helvetica", "size": 11} # 長度 8
]我們的任務是:
**找出文字最長的那個 `span`(也就是第 4 個),
並把整顆 dictionary 拿回來,
好讓我們可以讀它的 font 或 size。**
—
## 寫法 1:直覺的 List 推導式(會弄丟物件!)
很多人第一直覺會用推導式把長度抓出來:
# 抓出最長的長度是多少
max_length = max([len(span.get("text", "")) for span in spans])
print(max_length)
# 結果:8**盲點在哪裡?**
你只拿到了「最大長度是 8」這個數字,
但**你把原本那個 dictionary 弄丟了**!
如果你想拿到它的 `font`,你得再寫一個迴圈回去找它:
# 因為弄丟了,只好再找一次
for span in spans:
if len(span.get("text", "")) == max_length:
main_span = span
break這種寫法雖然直觀,但需要多寫 4~5 行程式碼,
而且其實迴圈跑了兩次(找最大值跑一次,回頭找人又跑一次)。
—
## 寫法 2:發號碼牌的做法(`enumerate`)
為了解決「弄丟物件」的問題,
我們可以幫每個長度配一個「號碼牌(Index)」:
# %%
# 1. 產生一個 (長度, index) 的比賽名單
lengths_with_index = [(len(span.get("text", "")), i) for i, span in enumerate(spans)]
# 內容會變這樣:[(2, 0), (2, 1), (4, 2), (8, 3)]
# 2. 叫 max() 找出裡面最大的一組 (預設會比對 第一個元素:長度)
max_length, max_index = max(lengths_with_index)
# 3. 靠著號碼牌 (max_index) 把原本的老大領回來
main_span = spans[max_index]
**好處在哪裡?**
邏輯非常嚴謹。「憑票根領東西」,
這是非常傳統且安全的工程寫法。
如果這是在 C++ 或者比較舊的語言裡,這幾乎是標準答案。
![為什麼 Python 要用 `max` 配合 `key=lambda`?從找最長文字的 Span 談起 ; #spans:list[dict] ; max(spans, key=lambda s: len(s.get("text", ""))) - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/06/20260602134826_0_ec8857.png)
## 寫法 3:最 Pythonic 的解答(`key=lambda`)
Python 的設計者早就發現了這個痛點:
我們真的很常需要「用某個標準評分,然後直接拿回那個人」。
於是他們允許在 `max()` 裡面傳入 `key` 參數:
# spans : list[dict]
main_span = max(spans, key=lambda s: len(s.get("text", "")))![為什麼 Python 要用 `max` 配合 `key=lambda`?從找最長文字的 Span 談起 ; #spans:list[dict] ; max(spans, key=lambda s: len(s.get("text", ""))) - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/06/20260602132524_0_c45d06.png)
你看,只有一行,清爽乾淨!我們來拆解它是怎麼運作的:
### 把它想成「選秀節目」的評分:
1. **`max(spans, …)`**:
`spans` 是參賽者名單。我們告訴函式:
「請從這些選手(字典實體)裡面幫我挑一個冠軍出來。」
2. **`key=`**:
這是大會規定的「評分標準」。
你不設定的話,`max()` 不知道要比什麼(字典不能直接比大小)。
3. **`lambda s: len(s.get(“text”, “”))`**:
這是一個沒有名字的簡短函式(匿名函式)。
它告訴裁判:「每當一個選手 `$s$` 走上前來,
你就量一下他身上 `text` 這個欄位的字串長度,這就是他的分數。」
### 運作過程的奧妙
* 裁判看完分數後,知道 8 分最高。
* 但裁判**不會回傳 8 分**。
他會把當初拿到 8 分的
**那個選手(物件實體)直接指給您**。
這就是整句魔法的關鍵:`max()` 靠 `key` 計算出分數,
但最後吐給您的,是原本陣列裡活生生的那個物件!
—
## 小結:我該用哪一種?
* 如果您覺得 `lambda` 像外星文,
或者在維護一個給多人看、且大家都不熟 Python 的腳本,
使用 **寫法 2 (`enumerate`)** 是完全可以的,
邏輯堅固且不易看錯。
* 但如果您要在 Python 的世界裡打滾,
看懂並學會使用 **寫法 3 (`max(…, key=…)`)** 是必須的,
因為它是處理「在字典列表 (`list[dict]`) 中,
依據特定欄位尋找極值物件」最乾淨、最優雅的標準答案。
推薦hahow線上學習python: https://igrape.net/30afN
## 進階補充 1:這跟 `sort()`、`min()` 完全一樣!
在 Python 中,只要遇到需要「比大小」的情境,
這套 API 設計全都是相通的。
學會了 `max()` 的 `key`,你就等於同時學會了其他函式:
# 1. 找最大(最長)
main_span = max(spans, key=lambda s: len(s.get("text", "")))
# 2. 找最小(最短)
shortest_span = min(spans, key=lambda s: len(s.get("text", "")))
# 3. 排序(依長度由短到長)
sorted_spans = sorted(spans, key=lambda s: len(s.get("text", "")))
# 4. 排序(依長度由長到短)
sorted_spans_desc = sorted(spans, key=lambda s: len(s.get("text", "")), reverse=True)## 進階補充 2:Lambda 可以加型別檢查 (Typing) 嗎?
因為 `lambda` 的設計初衷是「簡短、用完就丟」,
Python 官方語法**不允許**直接在匿名函式內部加上型別註記
(例如不能寫 `lambda s: dict -> int:`)。
如果你的專案對型別檢查(如 `mypy`)要求很嚴格,你可以怎麼做?
**方法 A:改用標準的 `def` 函式(官方 PEP 8 最推薦)**
這能完美支援 Typing,也提高可讀性:
from typing import Dict, Any
def get_span_length(s: Dict[str, Any]) -> int:
return len(s.get("text", ""))
main_span = max(spans, key=get_span_length)**方法 B:為裝載 lambda 的變數註記 `Callable`**
from typing import Callable, Dict, Any
# Callable[[傳入參數型別...], 回傳型別]
score_func: Callable[[Dict[str, Any]], int] = \
lambda s: len(s.get("text", ""))
main_span = max(spans, key=score_func)推薦hahow線上學習python: https://igrape.net/30afN










近期留言