
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。
不同的 argument 模式 (類型) 被認為是不同的調用,將被單獨 cache。
例如:f(a=1, b=2), f(b=2, a=1), 不同的 keyword argument 順序。
@functools.cache 只能與 Hashable 物件 一起使用(例如:Numbers, Strings, Tuples...)。
因為 @functools.cache 將結果存儲在 Dictionary 中,並以參數作為 keys。 因此它不能直接與 unhashable 類型一起使用(例如:Lists, Dictionaries, Sets...)。 如果函數參數是可變的並且它們的值發生變化,則會擾亂 cache 機制。
通常最好只 cached pickled、 serialized 或純數據的物件類型,以避免潛在的問題。
Pickling 是將 Python 物件轉換為可以存儲在磁盤或透過網路發送的 byte stream。 並非所有 Python 物件都可以 pickle。 例如:打開的檔案或網路物件無法進行 pickle。
範例 – 費波那契數, 測量時間
@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 的非常重要。
在計算 Fibonacci sequence 時,thread-safe 會變慢,原因是 lock 爭用。
在 Fibonacci sequence 計算中,許多函數調用相同的參數。 @lru_cache 可以透過 cached 結果,來顯著加快這些調用的速度。 然而使用 lock,cache 調用必須等待 lock 可用。 在 thread 較多且調用重疊程度較高的場景中,lock 的等待時間可能會覆蓋 cache 的優勢。
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 時,這非常有用。
過於頻繁地使用 cache_clear() 可能會抵消 cache 優勢。如果底層數據或函數行為發生變化,不經常清除 cache 可能會導致使用過時的結果。
執行結果:
範例 – 外部 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。
@property 和 @property.setter 幫助控制屬性的設定和檢索方式,而 @cached_property 幫助 optimize 屬性的計算。
當您需要控制或自定義屬性的設定或檢索方式時,請使用 @property 和 @property.setter。
@cached_property 要求每個實例上的 __dict__ 是 mutable mapping。 這意味著不適用於 Metaclasses,因為 Metaclasses 的 __dict__ 是 Class 名稱空間的 read-only proxies。
@cached_property 可能會干擾 Key-sharing dictionary (PEP 412) 的操作,導致實例 dictionary 中的空間使用量增加。 如果優先考慮節省空間的 Key-sharing,或者如果 mutable mapping 不可用,則可以通過在 @lru_cache() 之上使用 property() 來實現與 @cached_property 類似的效果。 這種組合將為方法提供 cache,並具有一些額外的靈活性,但代價是稍微複雜一些。
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