
functools 【可調用物件】
簡介
functools module 用於簡化任務的工具箱,提供了一系列工具來以函數/方法式編程風格處理函數。
@cache
@functools.cache(user_function) 用於向函數和方法新增記憶功能 (Memoization)。當相同的輸入再次出現時,函數的結果將被存儲和重用。 這可以提高經常使用相同參數的函數的調用性能。 @functools.cache 等效 @functools.lru_cache(maxsize=None, typed=False)
@functools.cache 等效 @functools.lru_cache(maxsize=None, typed=False)這會使用無限的 cache,並且不會刪除舊值 (不會主動刪除舊 cache)。
對於僅使用有限參數的函數的調用,會比 @functools.lru_cache 更節省 memory 且速度更快。 但是,請注意唯一參數的數量,@functools.cache 可能會填滿您的 memory。
範例 – 費波那契數, 測量時間
@cache 包裝了 fibonacci() 遞迴函數,先前計算的結果將被 cache 和重用,從而顯著加快計算速度。 如果沒有 cache,遞迴函數將進行大量的重複計算。
首先測量執行時間。之後,Python 將檢查給定參數的函數結果是否在 cache 中。 如果是,Python 返回 cache 的結果; 如果不是,Python 計算後,將結果存儲在 cache 中。
在此範例中,@functools.cache 必須為第一個 decorator (因此最後寫入)。
因為測量函數的實際執行時間更有意義,而不是返回 @cache 的結果。
時間複雜度表示法 (例如:O(a*b) 或 O(2^n)):
為我們提供了一個高級的、概括的概念,即算法的運行時間如何隨著輸入大小的增加而增加。 但它並沒有直接告訴我們算法的絕對運行速度有多快,因為這還取決於算法中執行的特定 operations。
如範例,Fibonacci sequence 函數正在遞迴計算 35 個斐波那契數。 如果沒有記憶性,這將涉及大量的冗餘計算。 當函數被記憶時,它只需要計算每個 Fibonacci sequence 一次。因此,雖然 Fibonacci sequence 的原始時間複雜度確實是 O(2^n),但記憶後的時間複雜度是 O(n)。
範例 – 昂貴操作
expcious_operation() 的結果將根據參數 a 和 b 進行 cache。 如果你使用相同的參數再次調用 expcious_operation(),Python 不會重新計算該函數,而是直接返回 cache 結果。
expense_operation() 函數運行兩個 nested loops, 這會導致執行一億次 iterations,每次 iteration 都涉及一次賦值操作。(記憶後的時間複雜度仍然維持 O(a*b))
範例 – 遞迴 vs 迴圈
@lru_cache
提供 LRU(Least Recently Used;最近最少使用) cache 的功能。 如果 cache 已滿,它將丟棄最近最少使用的項目。
maxsize:cache 最大的大小。 最多可保存maxsize個最近調用的 cache。如果設置為 None,則cache可以無限增長。typed:如果等於True,不同類型的參數將被單獨 cache。 例如:f(3),f(3.0)將被視為不同調用(int, float)。f(('answer', 3)),f(('answer', 3.0))將被視為相同調用(Tuple)。
範例 – Argument 類型差異
執行結果:
範例 – Thread-safe
decorator 預設情況下不是 Thread-safe。 在 multi-threaded 環境中,不同的 threads 可能會嘗試同時訪問或修改 cache,從而導致競爭條件,不可預測的結果。
因此,在此類似的環境中使用 decorator 時,應始終確保 Thread-safe。
在 multi-threaded 環境中使用 @lru_cache 時,確保 function operation, cache management 都是 thread-safe 的非常重要。
Non-thread safe:
cache_info()
cache_info() 提供有關 cache 使用情況的報告(非 memory 的報告)。
包含:
hits:在 cache 中找到所需結果的次數。 表示有hits次不需要計算該函數,因為結果已經從之前的計算中獲得。misses:在 cache 中找不到所需結果,表示調用函數的次數。maxsize:cache 可以容納的最大大小,表示最多可以存儲maxsize個結果。currsize:當前 cache 的大小,表示已經存儲currsize個結果。
cache_clear()
cache_clear() 用於手動清除所有 cache。 當您知道函數的結果可能會更改 (例如:結果取決於外部資源或狀態),或者您想要釋放 memory 時,這非常有用。
執行結果:
範例 – 外部 Data source 交互
考慮一個與某些外部 data source 交互的函數(例如:數據庫、文件或 API)。 如果該 data source 發生更改,您需要確保沒有使用已過時的 cached 結果。
@cached_property
@cached_property(func) 用於 Class 中的方法新增記憶功能,並轉換為屬性(即,無需括號即可訪問, 計算出來的值與 Class 實例相關聯)。
該屬性的值在首次訪問時計算一次,然後在 Class 實例的 lifetime (生命週期) 內,作為一般屬性進行 cached。這使得 @cached_property 適合用於與實例相關的昂貴計算。
它的工作原理如下:
首次訪問某個屬性時,Python 會檢查該名稱的屬性是否已存在。
如果不存在,則調用
@cached_property方法,並將結果存儲為同名的屬性。之後,cached 屬性的行為就像一般屬性一樣。 任何後續的讀取都將獲取 cached 值,任何寫入都將覆蓋 cached 值。
如果要清除 cached 值,應該使用
del刪除該屬性。 下次訪問該屬性時,@cached_property將再次運行。
@cached_property 是針對每個實例的,而不是對每個 Clas 的。(Clas 的每個實例都將擁有自己的屬性 cached)
因此如果您有多個 Clas 實例,它們可能會使用大量 memory。
Cached_property vs. Cache
你應該兩者都使用嗎? 在同一 Class 方法中,同時使用 @cached_property, @cache 通常是不必要的,並且可能會導致混亂。 這是因為它們是針對不同的場景而設計的:@cached_property 用於計算屬性值的方法,@cache 用於使用輸入,執行計算的函數。 然而,在某些情況下,使用兩者可能是有益的,例如:具有計算成本昂貴的 Class 方法,在內部調用另一個計算成本昂貴的函數。
總之,使用哪一種主要取決於您的具體用例、函數/方法的計算強度、函數/方法是否使用可變數據或有副作用、您環境的內存限制以及您的參數是否 函數/方法是可散列的。
@cached_property 優點:
當屬性計算量很大並且屬性被多次訪問時,它可以加速您的程序。
作為一個屬性,它不需要顯式調用函數或方法,從而使您的程式碼更清晰、更具可讀性。
目的用於建立唯讀屬性(請避免修改)。 cached 結果存儲在實例變數中。
@cache 優點:
當函數計算量很大並且使用相同的參數多次調用該函數,它可以加速您的程序。
它可以 cached 具有任意數量參數的函數的結果。
@cached_property 缺點:
它只能與不帶參數的實例方法一起使用(self 除外)。
@cache 缺點:
它只能與接受 Hashable 參數的函數/方法一起使用,因為 cache 是根據 dictionary 實現的。
共同缺點:
如果函數有依賴於 mutable 數據,cache 可能容易導致不正確的結果。(將繼續返回舊的 cached 值)
它使用 memory 來存儲 cache,這在大數據或 memory 有限的環境中可能會出現問題。
範例 – 基礎
範例 – Thread-safe
@functools.cached_property 是 thread-safe,這意味著它可以在 multithreaded 環境中安全使用。
如果沒有 thread-safe,一個 thread 可能會看到該屬性處於部分更新狀態,而另一個 thread 正在更新該屬性。 (看不到兩者之間的某些不一致狀態)
執行結果:
範例 – Mutable 物件
Mutable 物件 (可變物件) 在創建後可以更改 (例如:Lists, Dictionaries, Sets)。 這與 Numbers, Strings, Tuples...等 immutable 物件形成對比,這些物件在建立後就無法更改。
當 cached 結果是 Mutable 物件時,您需要小心。
如果您修改它,更改將持續存在。 如果您沒有意識到,這可能會導致意外行為。如果您打算修改它,為了避免意外行為,您應該立即製作一個副本。
Last updated
Was this helpful?