2026年8月30日 星期日

把智慧養殖計算交給lambda、map與filter

Python一下|第7篇

把智慧養殖計算交給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)
選擇原則:轉換邏輯有明確名稱或超過一小段時,使用具名函式;真正簡單且只用一次,才考慮lambda。

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))
教學門檻:25~30°C只用來展示Python篩選,不是正式文蛤養殖管理標準。篩選結果也只是「依規則挑出的資料」,仍要回到來源、時間、感測器及現場情境判讀。

六、串接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))
迭代器只能一路往前:mapfilter物件轉成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)
教材建議:先確定學生會寫一般迴圈,再介紹map、filter與生成式。能看懂一行程式不等於能維護它;可讀性比行數少更重要。

八、完整示範:養殖資料轉換與篩選

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並核對總筆數。

十一、學習檢核

  1. 函式名稱後有沒有括號,分別代表什麼?
  2. lambda最適合哪一類函式?
  3. map()filter()的目的有何不同?
  4. 為什麼它們的結果常要轉成List?
  5. 何時一般for迴圈反而比lambda更好?
  6. 場域資料被filter排除時,為什麼還應保存原因與數量?

十二、AI協作提示詞

請擔任Python函式與資料處理助教。
我有一組USR場域資料,希望練習lambda、map與filter。
請先詢問資料結構、轉換規則、篩選條件及是否需要保留淘汰原因。

請依序提供:
1. 最容易理解的一般for迴圈版本;
2. 具名函式搭配map/filter版本;
3. 若仍清楚,再提供lambda或生成式版本;
4. 比較三種寫法的可讀性與資料追溯能力。

不要為了縮成一行而犧牲可讀性,
也不要捏造真實養殖門檻、資料或USR成效。

十三、延伸閱讀

十四、本篇小結

Python把函式視為物件,因此能將函式傳給map()filter()。lambda適合短小、一次性的運算;複雜邏輯則應回到具名函式。場域程式的目標不是追求最少行數,而是讓轉換、篩選與排除理由都能被理解及追查。

好程式不只算得出答案,也要讓下一位學生、場域夥伴與未來的自己看得懂。
系列:《Python一下:從風土資料到智慧生活的程式設計》
上一篇:《巡查七座養殖場——break、continue與迴圈else》
下一篇:《用魚菜共生監測程式認識內部函式與Closure》
教學定位:國立虎尾科技大學USR × 智慧養殖資料 × 高階函式 × 可讀性

沒有留言:

張貼留言