專項練習 · 9 分鐘閱讀

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__)

保留原始身份

你的練習清單

常見問題

lru_cache 可以快取方法嗎?

可以,但 self 會成為鍵的一部分。要確保實例可雜湊,並注意快取不會意外保留不需要的物件。

partial 會複製函式嗎?

不會。它儲存原可呼叫物件的引用,以及要預填的參數。

什麼時候 reduce 不如迴圈清楚?

當累積過程有多個分支或需要提前退出時。帶名字的迴圈通常比密集的 reduce 表達式更易讀。

練習 functools

12 道題,覆蓋快取、偏函式參數、reduce 和包裝器元資料。

打開練習

繼續閱讀: