把智慧養殖計算交給lambda、map與filter
《從風土資料到智慧生活的程式設計》——把函式當成資料傳遞,讓批次轉換與篩選更有條理。
- 複習一般函式的參數與回傳值。
- 理解Python函式可以指定給變數、放入容器及傳給其他函式。
- 使用
lambda撰寫簡短匿名函式。 - 使用
map()批次轉換資料。 - 使用
filter()依條件篩選資料。 - 比較一般迴圈、map/filter與串列生成式的可讀性。
一、先從一般函式開始
假設感測器回傳攝氏溫度,我們想同時提供華氏溫度。最清楚的做法是先定義一般函式:
def celsius_to_fahrenheit(celsius):
"""把攝氏溫度轉為華氏溫度。"""
return celsius * 9 / 5 + 32
result = celsius_to_fahrenheit(28.5)
print(f"華氏溫度:{result:.1f}°F")def建立具名函式,celsius是參數,return把計算結果交回呼叫處。若邏輯需要名稱、說明文件、型別提示、測試或重複使用,具名函式通常是首選。
二、Python的函式也是物件
函式不只能被呼叫,也可以像其他資料一樣指定給變數。
def celsius_to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
converter = celsius_to_fahrenheit
print(converter)
print(converter(28.5))converter = celsius_to_fahrenheit是把函式本身交給變數;converter = celsius_to_fahrenheit(28.5)是先執行函式,再把結果交給變數。函式可以放進List
def show_temperature(value):
return f"水溫:{value:.1f}°C"
def show_oxygen(value):
return f"溶氧:{value:.1f} mg/L"
formatters = [show_temperature, show_oxygen]
print(formatters[0](28.5))
print(formatters[1](5.8))函式可以當成參數
def apply_rule(value, rule):
return rule(value)
def round_one_decimal(value):
return round(value, 1)
print(apply_rule(28.567, round_one_decimal))這類「接收函式或回傳函式」的函式常稱為高階函式。map()與filter()就是常見例子。
三、lambda:一次使用的簡短函式
lambda建立匿名函式,基本語法是:
lambda 參數: 表示式攝氏轉華氏可以寫成:
converter = lambda celsius: celsius * 9 / 5 + 32
print(converter(28.5))適合lambda
很短、單次使用、意義一眼可懂
不適合lambda
多步驟、需要說明、需要測試或重複使用
限制
函式主體只能是一個表示式
多個參數
daily_energy = lambda power, hours: power * hours
print(daily_energy(0.23, 10))lambda搭配sorted()
ponds = [
{"名稱": "一號池", "水溫": 28.5},
{"名稱": "二號池", "水溫": 27.9},
{"名稱": "三號池", "水溫": 29.1}
]
sorted_ponds = sorted(ponds, key=lambda pond: pond["水溫"])
for pond in sorted_ponds:
print(pond)這是lambda很典型的用途:只需要一個簡短函式告訴sorted()「要用哪個欄位排序」。
四、map():把同一轉換套用到每筆資料
map(函式, 可迭代資料)會把函式依序套用到每個元素。在Python 3中,map()回傳的是惰性迭代器,需要時才產生結果。
temperatures = [27.8, 28.5, 29.1]
fahrenheit_values = map(
lambda celsius: celsius * 9 / 5 + 32,
temperatures
)
print(list(fahrenheit_values))map搭配具名函式
def format_temperature(value):
return f"{value:.1f}°C"
temperatures = [27.8, 28.5, 29.1]
labels = list(map(format_temperature, temperatures))
print(labels)map處理多個可迭代資料
powers = [0.23, 0.30, 0.25]
hours = [10, 8, 12]
energy_values = map(
lambda power, hour: power * hour,
powers,
hours
)
print(list(energy_values))map()搭配多組資料時,會在最短的資料用完後停止。若不同List長度不一致,應先檢查資料是否真的能逐項對應。
五、filter():只保留條件成立的資料
filter(判斷函式, 可迭代資料)會保留判斷結果為True的元素,同樣回傳惰性迭代器。
temperatures = [27.8, None, 31.2, 28.5, 24.7]
valid_temperatures = filter(
lambda value: value is not None,
temperatures
)
print(list(valid_temperatures))篩選需要進一步確認的教學資料
temperatures = [27.8, 31.2, 28.5, 24.7, 29.1]
attention_values = filter(
lambda value: not 25 <= value <= 30,
temperatures
)
print(list(attention_values))六、串接filter()與map()
常見資料流程是先移除缺漏值,再轉換格式:
temperatures = [27.8, None, 28.5, None, 29.1]
valid_values = filter(
lambda value: value is not None,
temperatures
)
formatted_values = map(
lambda value: f"{value:.1f}°C",
valid_values
)
print(list(formatted_values))map或filter物件轉成List後,原迭代器通常已被消耗。若結果要重複使用,請及早轉成List保存。七、一般迴圈、map/filter與生成式怎麼選?
| 寫法 | 優點 | 適合情況 | 注意 |
|---|---|---|---|
| 一般for迴圈 | 流程清楚,容易除錯及加入多步驟 | 初學、複雜處理、需要紀錄原因 | 程式較長但未必是缺點 |
map() | 明確表達「每筆都轉換」 | 已有具名轉換函式 | 回傳迭代器 |
filter() | 明確表達「依條件保留」 | 已有清楚判斷函式 | 只保留元素,不直接說明淘汰原因 |
| 串列生成式 | 簡潔且符合Python常見風格 | 簡單轉換與單一篩選 | 條件過多會難讀 |
四種寫法完成同一件事
temperatures = [27.8, 31.2, 28.5]
# 一般迴圈
labels_loop = []
for value in temperatures:
labels_loop.append(f"{value:.1f}°C")
# map搭配具名函式
def format_temperature(value):
return f"{value:.1f}°C"
labels_map = list(map(format_temperature, temperatures))
# map搭配lambda
labels_lambda = list(map(lambda value: f"{value:.1f}°C", temperatures))
# 串列生成式
labels_comprehension = [f"{value:.1f}°C" for value in temperatures]
print(labels_loop)
print(labels_map)
print(labels_lambda)
print(labels_comprehension)八、完整示範:養殖資料轉換與篩選
records = [
{"場域": "一號場", "水溫": 28.5, "狀態": "正常"},
{"場域": "二號場", "水溫": None, "狀態": "維護中"},
{"場域": "三號場", "水溫": 31.2, "狀態": "正常"},
{"場域": "四號場", "水溫": 27.9, "狀態": "正常"}
]
def is_usable(record):
return record["狀態"] == "正常" and record["水溫"] is not None
def add_temperature_label(record):
return {
**record,
"水溫顯示": f'{record["水溫"]:.1f}°C',
"需確認": not 25 <= record["水溫"] <= 30
}
usable_records = filter(is_usable, records)
processed_records = list(map(add_temperature_label, usable_records))
for record in processed_records:
print(record)這裡使用具名函式,而不是把複雜Dict處理塞進lambda。{**record, ...}建立新的Dict,保留原始欄位並加入衍生欄位,避免直接修改原始紀錄。
九、常見錯誤
| 錯誤 | 原因 | 修正 |
|---|---|---|
印出<map object...> | map回傳迭代器 | 需要完整結果時用list() |
| lambda寫了多個陳述式 | lambda只能包含一個表示式 | 改用def具名函式 |
| 把函式呼叫結果傳給map | 寫成map(func(), data) | 傳入函式本身:map(func, data) |
| 第二次使用迭代器得到空結果 | 迭代器已被消耗 | 先轉成List保存 |
| filter後不知道哪些資料被丟掉 | 只保留True元素 | 場域資料需另記淘汰原因與數量 |
| 一行程式過度複雜 | 為縮短行數犧牲可讀性 | 拆成具名函式與中間變數 |
十、漸進式練習
基礎:lambda計算用電
建立接收功率與時間的lambda,回傳每日估計用電量;再比較一般函式版本何者較容易加上文件說明。
進階:先filter再map
從含有None的溶氧List中移除缺漏值,再把有效數值格式化為含單位的字串。
USR挑戰:保留被排除原因
比較單用filter與一般for迴圈:若要同時得到「可用資料」及「被排除資料與原因」,哪種寫法較清楚?實作兩個List並核對總筆數。
十一、學習檢核
- 函式名稱後有沒有括號,分別代表什麼?
- lambda最適合哪一類函式?
map()與filter()的目的有何不同?- 為什麼它們的結果常要轉成List?
- 何時一般for迴圈反而比lambda更好?
- 場域資料被filter排除時,為什麼還應保存原因與數量?
十二、AI協作提示詞
請擔任Python函式與資料處理助教。
我有一組USR場域資料,希望練習lambda、map與filter。
請先詢問資料結構、轉換規則、篩選條件及是否需要保留淘汰原因。
請依序提供:
1. 最容易理解的一般for迴圈版本;
2. 具名函式搭配map/filter版本;
3. 若仍清楚,再提供lambda或生成式版本;
4. 比較三種寫法的可讀性與資料追溯能力。
不要為了縮成一行而犧牲可讀性,
也不要捏造真實養殖門檻、資料或USR成效。十三、延伸閱讀
十四、本篇小結
Python把函式視為物件,因此能將函式傳給map()與filter()。lambda適合短小、一次性的運算;複雜邏輯則應回到具名函式。場域程式的目標不是追求最少行數,而是讓轉換、篩選與排除理由都能被理解及追查。
好程式不只算得出答案,也要讓下一位學生、場域夥伴與未來的自己看得懂。
沒有留言:
張貼留言