程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python3教程:functools模塊的用法

編輯:Python

functools 作用於函數的函數

functools 模塊提供用於調整或擴展函數和其他可調用對象的工具,而無需完全重寫它們。

裝飾器

partial 類是 functools 模塊提供的主要工具, 它可以用來“包裝”一個可調用的對象的默認參數。它產生的對象本身是可調用的,可以看作是原生函數。它所有的參數都與原來的相同,並且可以使用額外的位置參數或命名參數來調用。使用 partial 代替 lambda 來為函數提供默認參數,同時保留那些未指定的參數。

Partial 對象

下面列子是對 myfunc 方法的兩個 partial 對象,show_details() 用於輸出partial對象的 func 、 args 和 keywords 屬性:

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 的值,導致異常。

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():

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等於或大於被比較項返回-1、0或1。Python2.1開始引入了 富比較 方法API(__lt__(), __le()__, __eq__(), __ne__(), __gt__() __ge__()),用於執行比較操作返回一個布爾值。Python3中 __cmp__() 放棄支持這些新方法,由 functools 提供工具,以便於編寫符合Python3中新的比較需求的類。

富比較

富比較API旨在允許具有復雜比較的類以最有效的方式實現每種計算。但是,對於比較相對簡單的類,手動創建每種富比較方法沒有意義。total_ordering() 類裝飾器可以使被裝飾的類只需要定義 __lt__(),__le__().__gt__()__ge__() 中的其中一個和 __eq__(), 剩下的由該裝飾器自動提供。這簡化了定義所有富比較操作的工作量。

''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:711312441 尋找有志同道合的小伙伴,互幫互助,群裡還有不錯的視頻學習教程和PDF電子書! '''
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) 將被視為不同的值不同的調用。

Reduce方法

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() 裝飾器注冊一組通用函數基於函數的第一個參數的類型自動切換,類似於強類型語言中的函數重載。

''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流QQ群:711312441 尋找有志同道合的小伙伴,互幫互助,群裡還有不錯的視頻學習教程和PDF電子書! '''
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沒有與任何已注冊的泛型函數匹配,所以根據其類的繼承順序進行選擇。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved