functools 做用於函數的函數python
functools
模塊提供用於調整或擴展函數和其餘可調用對象的工具,而無需徹底重寫它們。算法
partial
類是 functools
模塊提供的主要工具, 它能夠用來「包裝」一個可調用的對象的默認參數。它產生的對象自己是可調用的,能夠看做是原生函數。它全部的參數都與原來的相同,而且可使用額外的位置參數或命名參數來調用。使用 partial
代替 lambda
來爲函數提供默認參數,同時保留那些未指定的參數。緩存
下面列子是對 myfunc
方法的兩個 partial
對象,show_details()
用於輸出partial對象的 func
、 args
和 keywords
屬性:app
import functools def myfunc(a, b=2): """Docstring for myfunc().""" print(' 傳入參數:', (a, b)) def show_details(name, f, is_partial=False): """Show details of a callable object.""" print('{}:'.format(name)) print(' object:', f) if not is_partial: print(' __name__:', f.__name__) if is_partial: print(' func:', f.func) print(' args:', f.args) print(' keywords:', f.keywords) return show_details('myfunc', myfunc) myfunc('a', 3) print() # # 給'b'從新設置一個不一樣的默認參數 # # 調用時仍需提供參數'a' p1 = functools.partial(myfunc, b=4) show_details('partial 修改關鍵字參數', p1, True) p1('傳入 a') p1('重寫 b', b=5) print() # # # 給 'a' 和 'b' 都設置默認參數. p2 = functools.partial(myfunc, '默認 a', b=99) show_details('partial 設置默認參數', p2, True) p2() p2(b='重寫 b') print() print('參數缺失時:') p1()
示例中最後調用第一個 partial
對象而沒有傳遞 a
的值,致使異常。ide
myfunc: object: <function myfunc at 0x00000180005077B8> __name__: myfunc 傳入參數: ('a', 3) partial 修改關鍵字參數: object: functools.partial(<function myfunc at 0x00000180005077B8>, b=4) func: <function myfunc at 0x00000180005077B8> args: () keywords: {'b': 4} 傳入參數: ('傳入 a', 4) 傳入參數: ('重寫 b', 5) partial 設置默認參數: object: functools.partial(<function myfunc at 0x00000180005077B8>, '默認 a', b=99) func: <function myfunc at 0x00000180005077B8> args: ('默認 a',) keywords: {'b': 99} 傳入參數: ('默認 a', 99) 傳入參數: ('默認 a', '重寫 b') 參數缺失時: Traceback (most recent call last): File "functools_partial.py", line 51, in <module> p1() TypeError: myfunc() missing 1 required positional argument: 'a'
默認狀況下, partial
對象沒有 __name__
和 __doc__
屬性。 這樣不利於被裝飾的函數進行調試。可使用 update_wrapper()
從原函數複製或新增屬性到 partial
對象。函數
import functools def myfunc(a, b=2): """Docstring for myfunc().""" print(' 傳入參數:', (a, b)) def show_details(name, f): """Show details of a callable object.""" print('{}:'.format(name)) print(' object:', f) print(' __name__:', end=' ') try: print(f.__name__) except AttributeError: print('(no __name__)') print(' __doc__', repr(f.__doc__)) print() show_details('myfunc', myfunc) p1 = functools.partial(myfunc, b=4) show_details('raw wrapper', p1) print('Updating wrapper:') print(' assign:', functools.WRAPPER_ASSIGNMENTS) print(' update:', functools.WRAPPER_UPDATES) print() functools.update_wrapper(p1, myfunc) show_details('updated wrapper', p1)
添加到裝飾器的屬性在 WRAPPER_ASSIGNMENTS
中定義,而 WRAPPER_UPDATES
列出要修改的值。工具
myfunc: object: <function myfunc at 0x000002315C123E18> __name__: myfunc __doc__ 'Docstring for myfunc().' raw wrapper: object: functools.partial(<function myfunc at 0x000002315C123E18>, b=4) __name__: (no __name__) __doc__ 'partial(func, *args, **keywords) - new function with partial application\n of the given arguments and keywords.\n' Updating wrapper: assign: ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') update: ('__dict__',) updated wrapper: object: functools.partial(<function myfunc at 0x000002315C123E18>, b=4) __name__: myfunc __doc__ 'Docstring for myfunc().'
partial適用於全部可調用可對象,並非僅可用於獨立函數。性能
import functools class MyClass: """Demonstration class for functools""" def __call__(self, e, f=6): "Docstring for MyClass.__call__" print(' called object with:', (self, e, f)) def show_details(name, f): """"Show details of a callable object.""" print('{}:'.format(name)) print(' object:', f) print(' __name__:', end=' ') try: print(f.__name__) except AttributeError: print('(no __name__)') print(' __doc__', repr(f.__doc__)) return o = MyClass() show_details('instance', o) o('e goes here') print() p = functools.partial(o, e='default for e', f=8) functools.update_wrapper(p, o) show_details('instance wrapper', p) p()
上面例子使用 MyClass
類的實例的 __call__()
方法建立了partial對象。照樣正常工做:測試
instance: object: <__main__.MyClass object at 0x000002DE7C2CD2E8> __name__: (no __name__) __doc__ 'Demonstration class for functools' called object with: (<__main__.MyClass object at 0x000002DE7C2CD2E8>, 'e goes here', 6) instance wrapper: object: functools.partial(<__main__.MyClass object at 0x000002DE7C2CD2E8>, e='default for e', f=8) __name__: (no __name__) __doc__ 'Demonstration class for functools' called object with: (<__main__.MyClass object at 0x000002DE7C2CD2E8>, 'default for e', 8)
partial()
返回一個能夠直接調用的對象, partialmethod()
返回一個可調用的爲某個對象準備的未綁定的方法。再下面例子中,同一個獨立函數被兩次添加到類 MyClass
屬性。使用 partialmethod()
生成 method1()
, partial()
生成 method2()
:ui
import functools def standalone(self, a=1, b=2): """獨立函數""" print(' called standalone with:', (self, a, b)) if self is not None: print(' self.attr =', self.attr) class MyClass: """"functools 示例類""" def __init__(self): self.attr = 'instance attribute' method1 = functools.partialmethod(standalone) method2 = functools.partial(standalone) o = MyClass() print('standalone') standalone(None) print() print('method1 as partialmethod') o.method1() print() print('method2 as partial') try: o.method2() except TypeError as err: print('ERROR: {}'.format(err))
method1()
能夠被 MyClass
實例調用,和普通類方法同樣,實例做爲第一個參數傳入。method2()
沒有被成功綁定爲類方法。所以其 self
參數必須顯式傳入,因此此例拋出 TypeError
異常:
standalone called standalone with: (None, 1, 2) method1 as partialmethod called standalone with: (<__main__.MyClass object at 0x00000214B4459B70>, 1, 2) self.attr = instance attribute method2 as partial ERROR: standalone() missing 1 required positional argument: 'self'
使用裝飾器時保持函數的屬性信息有時很是有用。可是使用裝飾器時不免會損失一些本來的功能信息。因此functools提供了 wraps()
裝飾器能夠經過 update_wrapper()
將原函數對象的指定屬性複製給包裝函數對象。
from functools import wraps def logged1(func): def with_login(*args, **kwargs): print(func.__name__ + "was called") return func(*args, **kwargs) return with_login @logged1 def f1(x): """ function doc""" return x + x * 1 def logged2(func): @wraps(func) def with_login(*args, **kwargs): print(func.__name__ + "was called") return func(*args, **kwargs) return with_login @logged2 def f2(x): """ function doc """ return x + x * 1 print("不使用functools.wraps時:") print("__name__: " + f1.__name__) print("__doc__: ", end=" ") print(f1.__doc__) print() print("使用functools.wraps時:") print("__name__: " + f2.__name__) print("__doc__: ", end=" ") print(f2.__doc__)
不使用functools.wraps時: __name__: with_login __doc__: None 使用functools.wraps時: __name__: f2 __doc__: function doc
在Python2以前,類中能夠定義 __cmp__()
方法,該方法根據對象是否小於、d等於或大於被比較項返回-一、0或1。Python2.1開始引入了 富比較 方法API(__lt__()
, __le()__
, __eq__()
, __ne__()
, __gt__()
和 __ge__()
),用於執行比較操做返回一個布爾值。Python3中 __cmp__()
放棄支持這些新方法,由 functools
提供工具,以便於編寫符合Python3中新的比較需求的類。
富比較API旨在容許具備複雜比較的類以最有效的方式實現每種計算。可是,對於比較相對簡單的類,手動建立每種富比較方法沒有意義。total_ordering()
類裝飾器可使被裝飾的類只須要定義 __lt__()
,__le__()
.__gt__()
和__ge__()
中的其中一個和 __eq__()
, 剩下的由該裝飾器自動提供。這簡化了定義全部富比較操做的工做量。
import functools import inspect from pprint import pprint @functools.total_ordering class MyObject: def __init__(self, val): self.val = val def __eq__(self, other): print(' testing __eq__({}, {})'.format( self.val, other.val)) return self.val == other.val def __gt__(self, other): print(' testing __gt__({}, {})'.format( self.val, other.val)) return self.val > other.val print("MyObject's Methods:\n") pprint(inspect.getmembers(MyObject, inspect.isfunction)) a = MyObject(1) b = MyObject(2) print('\nComparisons:') for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']: print('\n{:<6}:'.format(expr)) result = eval(expr) print(' result of {}: {}'.format(expr, result))
MyObject's Methods: [('__eq__', <function MyObject.__eq__ at 0x0000021DE4DB4048>), ('__ge__', <function _ge_from_gt at 0x0000021DDDE5D268>), ('__gt__', <function MyObject.__gt__ at 0x0000021DE4DB40D0>), ('__init__', <function MyObject.__init__ at 0x0000021DDDE877B8>), ('__le__', <function _le_from_gt at 0x0000021DDDE5D2F0>), ('__lt__', <function _lt_from_gt at 0x0000021DDDE5D1E0>)] Comparisons: a < b : testing __gt__(1, 2) testing __eq__(1, 2) result of a < b: True a <= b: testing __gt__(1, 2) result of a <= b: True a == b: testing __eq__(1, 2) result of a == b: False a >= b: testing __gt__(1, 2) testing __eq__(1, 2) result of a >= b: False a > b : testing __gt__(1, 2) result of a > b: False
雖然該裝飾器能很容易的建立徹底有序類型,但衍生出的比較函數執行的可能會更慢,以及產生更復雜的堆棧跟蹤。若是性能基準測試代表這是程序的瓶頸,則實現全部六個富比較函數可能會提升速度。
在Python3中已經廢棄了舊時的比較(cmp)函數,所以例如 sorted()
,min()
,max()
等方法不在支持 cmp
參數, 但仍然支持key函數。functools提供了 cmp_to_key()
用於將cmp函數轉換成key函數。
例如給定一個正整數列表,輸出用這些正整數可以拼接成的最大整數。若是是Python2的程序能夠是這樣:
L = [97, 13, 4, 246] def my_cmp(a, b): """ 將比較的兩個數字拼接成整數, 比較數值大小""" return int(str(b) + str(a)) - int(str(a) + str(b)) L.sort(cmp=my_cmp) print(''.join(map(str, L))) # 輸出 97424613
但Python3的 sort
函數已廢棄 cmp
參數,可使用 cmp_to_key
將cmp函數轉換成key函數:
from functools import cmp_to_key L = [97, 13, 4, 246] def my_cmp(a, b): """ 將比較的兩個數字拼接成整數, 比較數值大小""" return int(str(b) + str(a)) - int(str(a) + str(b)) L.sort(key=cmp_to_key(my_cmp)) print(''.join(map(str, L))) # 輸出 97424613
cmp
函數接收兩個參數,比較它們,若是小於返回負數,相等返回0,大於返回正數。 key
函數接收一個參數,返回用於排序的鍵。
lru_cache()
裝飾器是 緩存淘汰算法(最近最少使用)的一種實現。其使用函數的參數做爲key結果做爲value緩存在hash結構中(所以函數的參數必須是hashable),若是後續使用相同參數再次調用將從hash從返回結果。同時裝飾器還添加了檢查緩存轉態方法(cache_info()
)和清空緩存方法(cache_clear()
)給函數。
import functools @functools.lru_cache() def demo(a): print('called demo with {}'.format(a)) return a ^ 2 MAX = 2 print('初次調用:') for i in range(MAX): demo(i) print(demo.cache_info()) print('\n第二次調用:') for i in range(MAX + 1): demo(i) print(demo.cache_info()) print('\n清空緩存後:') demo.cache_clear() print(demo.cache_info()) print('\n再次調用:') for i in range(MAX): demo(i) print(demo.cache_info())
代碼中屢次調用 demo()
方法。首次調用後結果存在緩存中。cache_info()
返回一個命名元組,包括 hits
,misses
,maxsize
和 currsize
。當第二次調用時命中緩存的調用將直接返回緩存內容,cache_clear()
用於清空當前緩存。
初次調用: called demo with 0 called demo with 1 CacheInfo(hits=0, misses=2, maxsize=128, currsize=2) 第二次調用: called demo with 2 CacheInfo(hits=2, misses=3, maxsize=128, currsize=3) 清空緩存後: CacheInfo(hits=0, misses=0, maxsize=128, currsize=0) 再次調用: called demo with 0 called demo with 1 CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
爲了防止緩存在長時間運行的流程中無限制地增加,特別設置了 maxsize
參數, 默認是128,設置爲None時,則禁用LRU功能,緩存能夠無限增加。同時還提供了 typed
參數,用於設置是否區別參數類型,默認爲Fals。若是設置爲True,那麼相似如 demo(1)
和 demo(1.0)
將被視爲不一樣的值不一樣的調用。
Python3中取消了全局命名空間中的 reduce()
函數,將 reduced()
放到了 functools
模塊中,要使用 reduce()
的話,要先從 functools
中加載。
from functools import reduce print(reduce(lambda a, b: a + b, range(11))) # 計算1加到10 結果 55
在動態類型的語言(如Python)中,若是須要根據參數的類型執行不一樣的操做,簡單直接的方法就是檢查參數的類型。但在行爲差別明顯的狀況下須要分離成單獨的函數。 functools
提供 singledispatch()
裝飾器註冊一組通用函數基於函數的第一個參數的類型自動切換,相似於強類型語言中的函數重載。
import functools @functools.singledispatch def myfunc(arg): print('default myfunc({!r})'.format(arg)) @myfunc.register(int) def myfunc_int(arg): print('myfunc_int({})'.format(arg)) @myfunc.register(list) def myfunc_list(arg): print('myfunc_list({})'.format(' '.join(arg))) myfunc('string argument') myfunc(1) myfunc(2.3) myfunc(['a', 'b', 'c'])
被 singledispatch()
裝飾的函數是默認實現, 使用其 register()
屬性裝飾接收其餘類型參數的函數。調用時會根據 register()
中註冊的類型自動選擇實現函數。沒有則使用默認實現。
default myfunc('string argument') myfunc_int(1) default myfunc(2.3) myfunc_list(a b c)
另外再有繼承的狀況下,當類型沒有精確匹配時,將根據繼承順序,選擇最接近的類型。
import functools class A: pass class B(A): pass class C(A): pass class D(B): pass class E(C, D): pass @functools.singledispatch def myfunc(arg): print('default myfunc({})'.format(arg.__class__.__name__)) @myfunc.register(A) def myfunc_A(arg): print('myfunc_A({})'.format(arg.__class__.__name__)) @myfunc.register(B) def myfunc_B(arg): print('myfunc_B({})'.format(arg.__class__.__name__)) @myfunc.register(C) def myfunc_C(arg): print('myfunc_C({})'.format(arg.__class__.__name__)) myfunc(A()) myfunc(B()) myfunc(C()) myfunc(D()) myfunc(E())
myfunc_A(A) myfunc_B(B) myfunc_C(C) myfunc_B(D) myfunc_C(E)
在上面代碼中,類D和E沒有與任何已註冊的泛型函數匹配,因此根據其類的繼承順序進行選擇。