functools
--- 可呼叫物件上的高階函式與操作¶
原始碼:Lib/functools.py
functools
模組用於高階函式:作用於或回傳其他函式的函式。一般來說,任何可呼叫物件都可以被視為用於此模組的函式。
functools
模組定義了以下函式:
- @functools.cache(user_function)¶
簡單的輕量級無繫結函式快取 (Simple lightweight unbounded function cache)。有時稱之為"memoize"(記憶化)。
和
lru_cache(maxsize=None)
回傳相同的值,為函式引數建立一個字典查找的薄包裝器。因為它永遠不需要丟棄舊值,所以這比有大小限制的lru_cache()
更小、更快。舉例來說:
@cachedeffactorial(n):returnn*factorial(n-1)ifnelse1>>>factorial(10)# no previously cached result, makes 11 recursive calls3628800>>>factorial(5)# just looks up cached value result120>>>factorial(12)# makes two new recursive calls, the other 10 are cached479001600
該快取是執行緒安全的 (threadsafe),因此包裝的函式可以在多個執行緒中使用。這意味著底層資料結構在並行更新期間將保持連貫 (coherent)。
如果另一個執行緒在初始呼叫完成並快取之前進行額外的呼叫,則包裝的函式可能會被多次呼叫。
在 3.9 版被加入.
- @functools.cached_property(func)¶
將類別的一個方法轉換為屬性 (property),其值會計算一次,然後在實例的生命週期內快取為普通屬性。類似
property()
,但增加了快取機制。對於除使用該裝飾器的屬性外實質上幾乎是不可變 (immutable) 的實例,針對其所需要繁重計算會很有用。範例:
classDataSet:def__init__(self,sequence_of_numbers):self._data=tuple(sequence_of_numbers)@cached_propertydefstdev(self):returnstatistics.stdev(self._data)
cached_property()
的機制與property()
有所不同。除非定義了 setter,否則常規屬性會阻止屬性的寫入。相反地,cached_property 則允許寫入。cached_property 裝飾器僅在查找時且僅在同名屬性不存在時運行。當它運行時,cached_property 會寫入同名的屬性。後續屬性讀取和寫入優先於cached_property 方法,並且它的工作方式與普通屬性類似。
可以透過刪除屬性來清除快取的值,這使得cached_property 方法可以再次運行。
cached_property 無法防止多執行緒使用中可能出現的競爭條件 (race condition)。getter 函式可以在同一個實例上運行多次,最後一次運行會設定快取的值。所以快取的屬性最好是冪等的 (idempotent),或者在一個實例上運行多次不會有害,就不會有問題。如果同步是必要的,請在裝飾的 getter 函式內部或在快取的屬性存取周圍實作必要的鎖。
請注意,此裝飾器會干擾PEP 412 金鑰共用字典的操作。這意味著實例字典可能比平常佔用更多的空間。
此外,此裝飾器要求每個實例上的
__dict__
屬性是可變對映 (mutable mapping)。這意味著它不適用於某些型別,例如元類別 (metaclass)(因為型別實例上的__dict__
屬性是類別命名空間的唯讀代理),以及那些指定__slots__
而不包含__dict__
的型別作為有定義的插槽之一(因為此種類別根本不提供__dict__
屬性)。如果可變對映不可用或需要金鑰共享以節省空間,則也可以透過在
lru_cache()
之上堆疊property()
來實作類似於cached_property()
的效果。請參閱如何快取方法呼叫?以了解有關這與cached_property()
間不同之處的更多詳細資訊。在 3.8 版被加入.
在 3.12 版的變更:在 Python 3.12 之前,
cached_property
包含一個未以文件記錄的鎖,以確保在多執行緒使用中能保證 getter 函式對於每個實例只會執行一次。然而,鎖是針對每個屬性,而不是針對每個實例,這可能會導致無法被接受的嚴重鎖爭用 (lock contention)。在 Python 3.12+ 中,此鎖已被刪除。
- functools.cmp_to_key(func)¶
將舊式比較函式轉換為鍵函式,能與接受鍵函式的工具一起使用(例如
sorted()
、min()
、max()
、heapq.nlargest()
、heapq.nsmallest()
、itertools.groupby()
)。此函式主要作為轉換工具,用於從有支援使用比較函式的 Python 2 轉換成的程式。比較函式是任何能接受兩個引數、對它們進行比較,並回傳負數(小於)、零(相等)或正數(大於)的可呼叫物件。鍵函式是接受一個引數並回傳另一個用作排序鍵之值的可呼叫物件。
範例:
sorted(iterable,key=cmp_to_key(locale.strcoll))# locale-aware sort order
有關排序範例和簡短的排序教學,請參閱排序技法。
在 3.2 版被加入.
- @functools.lru_cache(user_function)¶
- @functools.lru_cache(maxsize=128,typed=False)
以記憶化可呼叫物件來包裝函式的裝飾器,最多可省去maxsize 個最近的呼叫。當使用相同引數定期呼叫繁重的或 I/O 密集的函式時,它可以節省時間。
該快取是執行緒安全的 (threadsafe),因此包裝的函式可以在多個執行緒中使用。這意味著底層資料結構在並行更新期間將保持連貫 (coherent)。
如果另一個執行緒在初始呼叫完成並快取之前進行額外的呼叫,則包裝的函式可能會被多次呼叫。
由於字典用於快取結果,因此函式的位置引數和關鍵字引數必須是可雜湊的。
不同的引數模式可以被認為是具有不同快取條目的不同呼叫。例如,
f(a=1,b=2)
和f(b=2,a=1)
的關鍵字引數順序不同,並且可能有兩個不同的快取條目。如果指定了user_function,則它必須是個可呼叫物件。這使得lru_cache 裝飾器能夠直接應用於使用者函式,將maxsize 保留為其預設值 128:
@lru_cachedefcount_vowels(sentence):returnsum(sentence.count(vowel)forvowelin'AEIOUaeiou')
如果maxsize 設定為
None
,則 LRU 功能將被停用,且快取可以無限制地成長。如果typed 設定為 true,不同型別的函式引數將會被單獨快取起來。如果typed 為 false,則實作通常會將它們視為等效呼叫,並且僅快取單一結果。(某些型別,例如str 和int 可能會被單獨快取起來,即使typed 為 false。)
請注意,型別特異性 (type specificity) 僅適用於函式的直接引數而不是其內容。純量 (scalar) 引數
Decimal(42)
和Fraction(42)
被視為具有不同結果的不同呼叫。相反地,元組引數('answer',Decimal(42))
和('answer',Fraction(42))
被視為等效。包裝的函式使用一個
cache_parameters()
函式來進行偵測,該函式回傳一個新的dict
以顯示maxsize 和typed 的值。這僅能顯示資訊,改變其值不會有任何效果。為了輔助測量快取的有效性並調整maxsize 參數,包裝的函式使用了一個
cache_info()
函式來做檢測,該函式會回傳一個附名元組來顯示hits、misses、maxsize 和currsize。裝飾器還提供了一個
cache_clear()
函式來清除或使快取失效。原本的底層函式可以透過
__wrapped__
屬性存取。這對於要自我檢查 (introspection)、繞過快取或使用不同的快取重新包裝函式時非常有用。快取會保留對引數和回傳值的參照,直到快取過時 (age out) 或快取被清除為止。
如果方法被快取起來,則
self
實例引數將包含在快取中。請參閱如何快取方法呼叫?當最近的呼叫是即將發生之呼叫的最佳預測因子時(例如新聞伺服器上最受歡迎的文章往往每天都會發生變化),LRU (least recently used) 快取能發揮最好的效果。快取的大小限制可確保快取不會在長時間運行的行程(例如 Web 伺服器)上無限制地成長。
一般來說,僅當你想要重複使用先前計算的值時才應使用 LRU 快取。因此,快取具有 side-effects 的函式、需要在每次呼叫時建立不同可變物件的函式(例如產生器和非同步函式)或不純函式(impure function,例如 time() 或 random())是沒有意義的。
靜態網頁內容的 LRU 快取範例:
@lru_cache(maxsize=32)defget_pep(num):'Retrieve text of a Python Enhancement Proposal'resource=f'https://peps.python.org/pep-{num:04d}'try:withurllib.request.urlopen(resource)ass:returns.read()excepturllib.error.HTTPError:return'Not Found'>>>fornin8,290,308,320,8,218,320,279,289,320,9991:...pep=get_pep(n)...print(n,len(pep))>>>get_pep.cache_info()CacheInfo(hits=3,misses=8,maxsize=32,currsize=8)
使用快取來實作動態規劃 (dynamic programming) 技法以有效率地計算費波那契數 (Fibonacci numbers) 的範例:
@lru_cache(maxsize=None)deffib(n):ifn<2:returnnreturnfib(n-1)+fib(n-2)>>>[fib(n)forninrange(16)][0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610]>>>fib.cache_info()CacheInfo(hits=28,misses=16,maxsize=None,currsize=16)
在 3.2 版被加入.
在 3.3 版的變更:新增typed 選項。
在 3.8 版的變更:新增user_function 選項。
在 3.9 版的變更:新增
cache_parameters()
函式。
- @functools.total_ordering¶
給定一個定義一個或多個 rich comparison 排序方法的類別,該類別裝飾器會提供其餘部分。這簡化了指定所有可能的 rich comparison 操作所涉及的工作:
類別必須定義
__lt__()
、__le__()
、__gt__()
或__ge__()
其中之一。此外,該類別應該提供__eq__()
方法。舉例來說:
@total_orderingclassStudent:def_is_valid_operand(self,other):return(hasattr(other,"lastname")andhasattr(other,"firstname"))def__eq__(self,other):ifnotself._is_valid_operand(other):returnNotImplementedreturn((self.lastname.lower(),self.firstname.lower())==(other.lastname.lower(),other.firstname.lower()))def__lt__(self,other):ifnotself._is_valid_operand(other):returnNotImplementedreturn((self.lastname.lower(),self.firstname.lower())<(other.lastname.lower(),other.firstname.lower()))
備註
雖然此裝飾器可以輕鬆建立能好好運作的完全有序型別 (totally ordered types),但它的確以衍生比較方法的執行速度較慢和堆疊追蹤 (stack trace) 較複雜做為其代價。如果效能基準測試顯示這是給定應用程式的效能瓶頸,那麼實作全部六種 rich comparison 方法通常能輕鬆地提升速度。
備註
此裝飾器不會嘗試覆寫類別或其超類別 (superclass)中宣告的方法。這意味著如果超類別定義了比較運算子,total_ordering 將不會再次實作它,即使原始方法是抽象的。
在 3.2 版被加入.
在 3.4 版的變更:現在支援從底層對於未識別型別的比較函式回傳
NotImplemented
。
- functools.partial(func,/,*args,**keywords)¶
回傳一個新的partial 物件,它在被呼叫時的行為類似於使用位置引數args 和關鍵字引數keywords 呼叫的func。如果向呼叫提供更多引數,它們將被附加到args。如果提供了額外的關鍵字引數,它們會擴充並覆寫keywords。大致相當於:
defpartial(func,/,*args,**keywords):defnewfunc(*fargs,**fkeywords):newkeywords={**keywords,**fkeywords}returnfunc(*args,*fargs,**newkeywords)newfunc.func=funcnewfunc.args=argsnewfunc.keywords=keywordsreturnnewfunc
partial()
用於部分函式應用程序,它「凍結」函式引數和/或關鍵字的某些部分,從而產生具有簡化簽名的新物件。例如,partial()
可用來建立可呼叫函式,其行為類似於int()
函式,其中base 引數預設為 2:>>>fromfunctoolsimportpartial>>>basetwo=partial(int,base=2)>>>basetwo.__doc__='Convert base 2 string to an int.'>>>basetwo('10010')18
- classfunctools.partialmethod(func,/,*args,**keywords)¶
回傳一個新的
partialmethod
描述器 (descriptor),其行為類似於partial
,只不過它被設計為用於方法定義而不能直接呼叫。func 必須是一個descriptor 或可呼叫物件(兩者兼具的物件,就像普通函式一樣,會被當作描述器處理)。
當func 是描述器時(例如普通的 Python 函式、
classmethod()
、staticmethod()
、abstractmethod()
或partialmethod
的另一個實例),對__get__
的呼叫將被委託 (delegated) 給底層描述器,且一個適當的partial 物件會被作為結果回傳。當func 是非描述器可呼叫物件 (non-descriptor callable) 時,會動態建立適當的繫結方法 (bound method)。當被作為方法使用時,其行為類似於普通的 Python 函式:self 引數將作為第一個位置引數插入,甚至會在提供給
partialmethod
建構函式的args 和keywords 的前面。範例:
>>>classCell:...def__init__(self):...self._alive=False...@property...defalive(self):...returnself._alive...defset_state(self,state):...self._alive=bool(state)...set_alive=partialmethod(set_state,True)...set_dead=partialmethod(set_state,False)...>>>c=Cell()>>>c.aliveFalse>>>c.set_alive()>>>c.aliveTrue
在 3.4 版被加入.
- functools.reduce(function,iterable,[initial,]/)¶
從左到右,將兩個引數的function 累加運用到iterable 的項目上,從而將可疊代物件減少為單一值。例如,
reduce(lambdax,y:x+y,[1,2,3,4,5])
會計算出((((1+2)+3)+4)+5)
。左邊的引數x 是累積值,右邊的引數y 是來自iterable 的更新值。如果可選的initial 存在,則在計算中會將其放置在可疊代物件的項目之前,並在可疊代物件為空時作為預設值。如果未給定initial 且iterable 僅包含一個項目,則回傳第一個項目。大致相當於:
initial_missing=object()defreduce(function,iterable,initial=initial_missing,/):it=iter(iterable)ifinitialisinitial_missing:value=next(it)else:value=initialforelementinit:value=function(value,element)returnvalue
請參閱
itertools.accumulate()
以了解產生 (yield) 所有中間值 (intermediate value) 的疊代器。
- @functools.singledispatch¶
若要定義泛型函式,請使用
@singledispatch
裝飾器對其裝飾。請注意,使用@singledispatch
定義函式時,分派調度 (dispatch) 是發生在第一個引數的型別上:>>>fromfunctoolsimportsingledispatch>>>@singledispatch...deffun(arg,verbose=False):...ifverbose:...print("Let me just say,",end=" ")...print(arg)
若要為函式新增過載實作,請使用泛型函式的
register()
屬性,該屬性可用作裝飾器。對於以型別來註釋的函式,裝飾器將自動推斷第一個引數的型別:>>>@fun.register...def_(arg:int,verbose=False):...ifverbose:...print("Strength in numbers, eh?",end=" ")...print(arg)...>>>@fun.register...def_(arg:list,verbose=False):...ifverbose:...print("Enumerate this:")...fori,eleminenumerate(arg):...print(i,elem)
也可以使用
types.UnionType
和typing.Union
:>>>@fun.register...def_(arg:int|float,verbose=False):...ifverbose:...print("Strength in numbers, eh?",end=" ")...print(arg)...>>>fromtypingimportUnion>>>@fun.register...def_(arg:Union[list,set],verbose=False):...ifverbose:...print("Enumerate this:")...fori,eleminenumerate(arg):...print(i,elem)...
對於不使用型別註釋的程式碼,可以將適當的型別引數明確傳遞給裝飾器本身:
>>>@fun.register(complex)...def_(arg,verbose=False):...ifverbose:...print("Better than complicated.",end=" ")...print(arg.real,arg.imag)...
For code that dispatches on a collections type (e.g.,
list
), but wantsto typehint the items of the collection (e.g.,list[int]
), thedispatch type should be passed explicitly to the decorator itself with thetypehint going into the function definition:>>>@fun.register(list)...def_(arg:list[int],verbose=False):...ifverbose:...print("Enumerate this:")...fori,eleminenumerate(arg):...print(i,elem)
備註
At runtime the function will dispatch on an instance of a list regardlessof the type contained within the list i.e.
[1,2,3]
will bedispatched the same as["foo","bar","baz"]
. The annotationprovided in this example is for static type checkers only and has noruntime impact.若要啟用註冊lambdas 和預先存在的函式,
register()
屬性也能以函式形式使用:>>>defnothing(arg,verbose=False):...print("Nothing.")...>>>fun.register(type(None),nothing)
register()
屬性回傳未加裝飾器的函式。這讓使得裝飾器堆疊 (decorator stacking)、pickling
以及為每個變體獨立建立單元測試成為可能:>>>@fun.register(float)...@fun.register(Decimal)...deffun_num(arg,verbose=False):...ifverbose:...print("Half of your number:",end=" ")...print(arg/2)...>>>fun_numisfunFalse
呼叫時,泛型函式會分派第一個引數的型別:
>>>fun("Hello, world.")Hello, world.>>>fun("test.",verbose=True)Let me just say, test.>>>fun(42,verbose=True)Strength in numbers, eh? 42>>>fun(['spam','spam','eggs','spam'],verbose=True)Enumerate this:0 spam1 spam2 eggs3 spam>>>fun(None)Nothing.>>>fun(1.23)0.615
如果沒有為特定型別註冊實作,則使用其方法解析順序 (method resolution order) 來尋找更通用的實作。用
@singledispatch
裝飾的原始函式是為基底object
型別註冊的,這意味著如果沒有找到更好的實作就會使用它。如果一個實作有被註冊到一個抽象基底類別,則基底類別的虛擬子類別將被分派到該實作:
>>>fromcollections.abcimportMapping>>>@fun.register...def_(arg:Mapping,verbose=False):...ifverbose:...print("Keys & Values")...forkey,valueinarg.items():...print(key,"=>",value)...>>>fun({"a":"b"})a => b
若要檢查泛型函式將為給定型別選擇哪種實作,請使用
dispatch()
屬性:>>>fun.dispatch(float)<function fun_num at 0x1035a2840>>>>fun.dispatch(dict)# note: default implementation<function fun at 0x103fe0000>
若要存取所有已註冊的實作,請使用唯讀
registry
屬性:>>>fun.registry.keys()dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>, <class 'decimal.Decimal'>, <class 'list'>, <class 'float'>])>>>fun.registry[float]<function fun_num at 0x1035a2840>>>>fun.registry[object]<function fun at 0x103fe0000>
在 3.4 版被加入.
在 3.7 版的變更:
register()
屬性現在支援使用型別註釋。在 3.11 版的變更:
register()
屬性現在支援以types.UnionType
和typing.Union
作為型別註釋。
- classfunctools.singledispatchmethod(func)¶
若要定義泛型方法,請使用
@singledispatchmethod
裝飾器對其裝飾。請注意,使用@singledispatchmethod
定義函式時,分派調度是發生在第一個非self 或非cls 引數的型別上:classNegator:@singledispatchmethoddefneg(self,arg):raiseNotImplementedError("Cannot negate a")@neg.registerdef_(self,arg:int):return-arg@neg.registerdef_(self,arg:bool):returnnotarg
@singledispatchmethod
支援與其他裝飾器巢狀使用 (nesting),例如@classmethod
。請注意,為了使dispatcher.register
可用,singledispatchmethod
必須是最外面的裝飾器。以下範例是Negator
類別,其neg
方法繫結到該類別,而不是該類別的實例:classNegator:@singledispatchmethod@classmethoddefneg(cls,arg):raiseNotImplementedError("Cannot negate a")@neg.register@classmethoddef_(cls,arg:int):return-arg@neg.register@classmethoddef_(cls,arg:bool):returnnotarg
相同的模式可用於其他類似的裝飾器:
@staticmethod
、@abstractmethod
等。在 3.8 版被加入.
- functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)¶
更新wrapper 函式,使其看起來像wrapped 函式。可選引數是元組,用於指定原始函式的哪些屬性直接賦值給包裝函式上的匹配屬性,以及包裝函式的哪些屬性使用原始函式中的對應屬性進行更新。這些引數的預設值是模組層級的常數
WRAPPER_ASSIGNMENTS
(它賦值給包裝函式的__module__
、__name__
、__qualname__
、__annotations__
、__type_params__
和__doc__
文件字串 (docstring))和WRAPPER_UPDATES
(更新包裝器函式的__dict__
,即實例字典)。為了允許出於內省 (introspection) 和其他目的所對原始函式的存取(例如繞過快取裝飾器,如
lru_cache()
),此函式會自動向包裝器新增__wrapped__
屬性,該包裝器參照被包裝的函式。此函式的主要用途是在decorator 函式中,它包裝函式並回傳包裝器。如果包裝器函式未更新,則回傳函式的元資料 (metadata) 將反映包裝器定義而非原始函式定義,這通常不太會有幫助。
update_wrapper()
可以與函式以外的可呼叫物件一起使用。被包裝的物件中缺少的assigned 或updated 中指定的任何屬性都將被忽略(即此函式不會嘗試在包裝器函式上設定它們)。如果包裝函式本身缺少updated 中指定的任何屬性,仍然會引發AttributeError
。在 3.2 版的變更:現在會自動新增
__wrapped__
屬性。現在預設會複製__annotations__
屬性。缺少的屬性不再觸發AttributeError
。在 3.4 版的變更:
__wrapped__
屬性現在都會參照包裝函式,即便函式有定義__wrapped__
屬性。(參見bpo-17482)在 3.12 版的變更:現在預設會複製
__type_params__
屬性。
- @functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)¶
這是一個方便的函式,用於在定義包裝器函式時呼叫
update_wrapper()
作為函式裝飾器。它相當於partial(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated)
。例如:>>>fromfunctoolsimportwraps>>>defmy_decorator(f):...@wraps(f)...defwrapper(*args,**kwds):...print('Calling decorated function')...returnf(*args,**kwds)...returnwrapper...>>>@my_decorator...defexample():..."""Docstring"""...print('Called example function')...>>>example()Calling decorated functionCalled example function>>>example.__name__'example'>>>example.__doc__'Docstring'
如果不使用這個裝飾器工廠 (decorator factory),範例函式的名稱將會是
'wrapper'
,並且原始example()
的文件字串將會遺失。
partial
物件¶
partial
物件是由partial()
所建立的可呼叫物件。它們有三個唯讀屬性:
partial
物件與函式物件類似,因為它們是可呼叫的、可弱參照的 (weak referencable) 且可以具有屬性。有一些重要的區別,例如,__name__
和function.__doc__
屬性不會自動建立。此外,類別中定義的partial
物件的行為類似於靜態方法,並且在實例屬性查找期間不會轉換為繫結方法。