查價函式一天跑幾千次,價格卻從不變。加個快取只要一行。快取什麼時候會悄悄變成 bug?
functools 收集了操作函式的工具。lru_cache 記住結果,partial 提前固定部分參數,reduce 把序列摺疊成一個值。它們都很小,卻能減少重複的包裝程式碼。 你還會觀察快取命中、綁定關鍵字參數,並按型別分派。
快取重複呼叫
lru_cache 會按參數儲存結果。參數相同的第二次呼叫可以直接回傳快取值,不再執行函式體。快取需要無上限增長時,可以使用 maxsize=None。
from functools import lru_cache
@lru_cache(maxsize=None)
def square(n):
print("calculating", n)
return n * n
print(square(3))
print(square(3))計算一次,復用兩次
用 partial 固定參數
partial 會建立一個新的可呼叫物件,其中一些參數已經填好。剩下的參數稍後提供。它適合把通用函式改造成特定用途,例如從 power 構造 square。
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(5))專門化的函式
用 reduce 摺疊
reduce 從左到右累積應用一個二元函式。可選的初始值用來播種累加器。操作天然是摺疊時可以使用它;邏輯複雜時,清楚的迴圈往往更好。
from functools import reduce
print(reduce(lambda total, n: total + n, [1, 2, 3], 0))多個值,一個結果
只快取純函式和可雜湊參數
lru_cache 用參數當鍵,所以每個參數都必須可雜湊。它還假設函式是純的:相同輸入應得到相同輸出。cache_info() 可以看到命中和未命中;測試裡狀態改變時,可以用 cache_clear() 清掉舊結果。
from functools import lru_cache
calls = []
@lru_cache(maxsize=None)
def delivery_fee(city, weight):
calls.append(city)
return 4 if weight <= 2 else 7
print(delivery_fee("Taipei", 1.5))
print(delivery_fee("Taipei", 1.5))
print(delivery_fee("Tainan", 1.5))
print(len(calls), delivery_fee.cache_info().hits)命中時不再執行函式體
用 partial 固定關鍵字參數
partial 可以預填位置參數或關鍵字參數,回傳一個新的可呼叫物件。它適合總是需要同一前綴或同一格式化器的回呼。原函式不受影響,partial 還會保留綁定參數的引用,便於檢查。
from functools import partial
def render(template, name, *, prefix=""):
return f"{prefix}{template.format(name=name)}"
greet = partial(render, "Hello, {name}!", prefix="> ")
print(greet("Maya"))
print(greet.func.__name__, greet.args, greet.keywords)帶固定上下文的新函式
用 singledispatch 按型別分派
singledispatch 根據第一個參數的型別選擇實現。通用函式是兜底,register() 添加更具體的處理器。它能把型別分支從長長的 if/elif 裡拿出來,但不能代替對不可信輸入的驗證。
from functools import singledispatch
@singledispatch
def label(value):
return "other"
@label.register
def _(value: int):
return f"int:{value}"
@label.register
def _(value: str):
return f"str:{value}"
print(label(3))
print(label("tea"))
print(label(3.5))一個名字,多個型別處理器
本課要點
- lru_cache 按函式參數快取結果。
- 結果依賴變化的外部狀態時,必須清除快取。
- partial 固定參數並回傳新的可呼叫物件。
- reduce 把可迭代物件摺疊成一個值。
- 邏輯複雜時,短迴圈可能比 reduce 更清楚。
- 快取純函式和可雜湊呼叫,partial 固定上下文,singledispatch 替代型別分支。
常見錯誤
- 快取結果依賴可變全局狀態的函式。
- 以為 partial 會立刻呼叫函式。
- reduce 的累加器型別重要時,忘記提供初始值。
- 快取讀取可變全局狀態的函式,卻以為結果會自動失效。
用 lru_cache 快取 square 函式,再用 partial(power, exponent=2) 構造一個 square。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。