Python functools 練習:快取、偏函式與歸約
functools 的重點是改造函式,而不是重寫函式。你可以快取昂貴結果、固定一個參數、把序列摺疊成一個值,或者在包裝可呼叫物件時保留元資料。
快取純函式
lru_cache 按呼叫參數儲存結果。它最適合參數可雜湊的純函式。如果回傳可變物件並被修改,呼叫方之間可能共用狀態,所以應回傳副本,或避免快取可變結果。
from functools import lru_cache
@lru_cache(maxsize=None)
def square(n):
return n * n
print(square(4))
print(square(4))相同輸入,復用結果
查看快取,而不是猜測
cache_info 報告命中、未命中和目前大小。用它確認重複呼叫是否真的復用了結果。如果命中率一直很低,快取可能只增加記憶體,沒有節省計算。
from functools import lru_cache
@lru_cache(maxsize=None)
def double(n):
return n * 2
double(2)
double(2)
print(double.cache_info().hits)測量快取的效果
用 partial 固定參數
partial 回傳一個已經填入部分參數的新可呼叫物件,不會立刻呼叫函式。想保留前面的位置參數時,使用關鍵字參數會更清楚。
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(6))專門化的可呼叫物件
歸約時給出明確初值
reduce 每次合併兩個元素。提供初值可以讓空序列的結果可預測,也讓累加器的型別更明確。沒有初值時,空序列會拋出 TypeError。
from functools import reduce
numbers = [1, 2, 3, 4]
print(reduce(lambda total, n: total + n, numbers, 0))帶安全初值的歸約
包裝函式時保留元資料
回傳替代函式的裝飾器通常應使用 wraps。它會複製原函式的名稱和文件字串,讓日誌、help 輸出和偵錯資訊保持可讀。
from functools import wraps
def announce(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@announce
def greet():
"""Say hello."""
return "hi"
print(greet.__name__)保留原始身份
你的練習清單
- 快取參數可雜湊的純函式。
- 用 cache_info 檢查快取是否真的有幫助。
- 用 partial 固定參數,而不是立即呼叫。
- 可能遇到空輸入時給 reduce 提供初值。
- 替換函式的裝飾器應使用 functools.wraps。
常見問題
lru_cache 可以快取方法嗎?
可以,但 self 會成為鍵的一部分。要確保實例可雜湊,並注意快取不會意外保留不需要的物件。
partial 會複製函式嗎?
不會。它儲存原可呼叫物件的引用,以及要預填的參數。
什麼時候 reduce 不如迴圈清楚?
當累積過程有多個分支或需要提前退出時。帶名字的迴圈通常比密集的 reduce 表達式更易讀。
練習 functools
12 道題,覆蓋快取、偏函式參數、reduce 和包裝器元資料。
繼續閱讀: