天天看點

Python 中提供高階函數的 functools 子產品

Python自帶的 functools 子產品提供了一些常用的高階函數,也就是用于處理其它函數的特殊函數。換言之,就是能使用該子產品對可調用對象進行處理。英文文檔

一、functools子產品函數概覽

  • functools.cmp_to_key(func)
  • functools.total_ordering(cls)
  • functools.reduce(function, iterable[, initializer])
  • functools.partial(func[, args][, *keywords])
  • functools.update_wrapper(wrapper, wrapped[, assigned][, updated])
  • functools.wraps(wrapped[, assigned][, updated])
  • functools.lru_cache(maxsize=128, typed=False)
  • functools.partialmethod(func, *args, **keywords)
  • functools.singledispatch(default)

二、functools.cmp_to_key()

文法:

functools.cmp_to_key(func) 

該函數用于将舊式的比較函數轉換為關鍵字函數。

舊式的比較函數:接收兩個參數,傳回比較的結果。傳回值小于零則前者小于後者,傳回值大于零則相反,傳回值等于零則兩者相等。

關鍵字函數:接收一個參數,傳回其對應的可比較對象。例如 sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() 都可作為關鍵字函數。

在 Python 3 中,有很多地方都不再支援舊式的比較函數,此時可以使用 cmp_to_key() 進行轉換。

示例:

1

sorted

(iterable, key

=

cmp_to_key(cmp_func))

三、functools.total_ordering()

版本3.2中新增。
版本3.4中修改:如果是不能識别的類型,現在支援從底層比較函數傳回NotImplemented。
           

文法:

functools.total_ordering(cls) 

這是一個類裝飾器,用于自動實作類的比較運算。

隻需要在類中實作 __eq__() 方法和以下方法中的任意一個 __lt__(), __le__(), __gt__(), __ge__(),那麼 total_ordering() 就能自動幫我們實作餘下的幾種比較運算。

示例:

1

2

3

4

5

6

7

8

@total_ordering

class

Student:

def

__eq__(

self

, other):

return

((

self

.lastname.lower(),

self

.firstname.lower())

=

=

(other.lastname.lower(), other.firstname.lower()))

def

__lt__(

self

, other):

return

((

self

.lastname.lower(),

self

.firstname.lower()) <

(other.lastname.lower(), other.firstname.lower()))

給定的一個類定義了一個或多個富比較方法,該類裝飾器提供剩下的。這簡化了指定所有富比較操作的工作量。

注意:雖然該裝飾器能很容易的建立行為良好的完全有序類型,但會導緻衍生出的比較函數執行的更慢,以及更複雜的堆棧跟蹤。如果性能基準測試表明這是程式的瓶頸,則實作所有六個富比較函數可能會是提高速度的方式。

四、functools.reduce()

文法:

functools.reduce(function, iterable[, initializer]) 

該函數與 Python 内置的 reduce() 函數相同,主要用于編寫相容 Python 3 的代碼。

五、functools.partial()

文法:

functools.partial(func[, *args][, **keywords]) 

該函數傳回一個 partial 對象,調用該對象的效果相當于調用 func 函數,并傳入位置參數 args 和關鍵字參數 keywords 。如果調用該對象時傳入了位置參數,則這些參數會被添加到 args 中。如果傳入了關鍵字參數,則會被添加到 keywords 中。

partial() 函數的等價實作大緻如下:

1

2

3

4

5

6

7

8

9

def

partial(func,

*

args,

*

*

keywords):

def

newfunc(

*

fargs,

*

*

fkeywords):

newkeywords

=

keywords.copy()

newkeywords.update(fkeywords)

return

func(

*

(args

+

fargs),

*

*

newkeywords)

newfunc.func

=

func

newfunc.args

=

args

newfunc.keywords

=

keywords

return

newfunc

partial() 函數主要用于“當機”某個函數的部分參數,傳回一個參數更少、使用更簡單的函數對象。

示例:

1

2

3

4

5

>>>

from

functools

import

partial

>>> basetwo

=

partial(

int

, base

=

2

)

>>> basetwo.__doc__

=

'Convert base 2 string to an int.'

>>> basetwo(

'10010'

)

18

partial()

建立可調用的 partial 對象。它們有三個隻讀屬性:

5.1 partial.func

一個可調用的對象或函數。調用partial對象會轉為使用新的參數和關鍵字參數調用func。

5.2 partial.args

最左邊的位置參數會優先作為位置參數提供給partial對象調用。

5.3 partial.keywords

partial 對象被調用時提供關鍵字參數。

partial 對象與函數對象類似,它們可以被調用,有弱引用,并且可以有屬性。但有一些重要的差別。對于執行個體,

__name__

__doc__

屬性不會自動建立。同時,在類中定義的partial對象的行為類似靜态方法,在執行個體屬性查找時,不會轉換為綁定方法。

六、functools.update_wrapper()

文法:

functools.update_wrapper(wrapper, wrapped[, assigned][, updated]) 

該函數用于更新包裝函數(wrapper),使它看起來像原函數一樣。可選的參數是一個元組,assigned 元組指定要直接使用原函數的值進行替換的屬性,updated 元組指定要對照原函數進行更新的屬性。這兩個參數的預設值分别是子產品級别的常量:WRAPPER_ASSIGNMENTS 和 WRAPPER_UPDATES。前者指定了對包裝函數的 __name__, __module__, __doc__ 屬性進行直接指派,而後者指定了對包裝函數的 __dict__ 屬性進行更新。

該函數主要用于裝飾器函數的定義中,置于包裝函數之前。如果沒有對包裝函數進行更新,那麼被裝飾後的函數所具有的元資訊就會變為包裝函數的元資訊,而不是原函數的元資訊。

七、functools.wraps()

文法:

functools.wraps(wrapped[, assigned][, updated]) 

wraps() 簡化了 update_wrapper() 函數的調用。它等價于 partial(update_wrapper, wrapped=wrapped, assigned, updated=updated)。

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

>>>

from

functools

import

wraps

>>>

def

my_decorator(f):

...   @wraps(f)

...  

def

wrapper(

*

args,

*

*

kwds):

...    

print

'Calling decorated function'

...    

return

f(

*

args,

*

*

kwds)

...  

return

wrapper

>>> @my_decorator

...

def

example():

...  

"""Docstring"""

...  

print

'Called example function'

>>> example()

Calling decorated function

Called example function

>>> example.__name__

'example'

>>> example.__doc__

'Docstring'

如果不使用這個函數,示例中的函數名就會變成 wrapper ,并且原函數 example() 的說明文檔(docstring)就會丢失。

八、@functools.lru_cache(maxsize=128, typed=False)

版本3.2中新增。
版本3.3中修改:增加可選參數typed參數。
           

裝飾器用一個有記憶的調用包裝一個函數,它可以儲存最近maxsize次調用。當使用同樣的參數定期調用費時或I/O綁定的函數時,它可以節省時間。

因為使用字典緩存結果,是以函數的位置和關鍵字參數必須是hashable。

如果maxsize設定為None,則禁用LRU功能,并且緩存可以無限增長。當maxsize設定為$ 2^n $時,性能最佳。

如果typed設定為真,則不同類型的函數參數會分别緩存。例如,

f(3)

f(3.0)

将視為不同結果的不同調用。

為了幫助測量緩存的有效性并調整maxsize參數,包裝函數使用

cache_info()

函數傳回一個命名元組,包括hits,misses,maxsize和currsize。在多線程環境中,hits和misses是近似值。

裝飾器還提供了

cache_clear()

函數用于清除緩存,或者讓緩存失效。

原始的底層函數通過wrapped屬性通路。這對于内省,繞過緩存,或者重新裝飾函數很有用。

當最近調用是即将調用的最佳調用因子時(例如,新聞伺服器上的最受歡迎文章常常每天改變),LRU(least recently used)緩存效果最好。緩存的大小限制確定緩存不會在長時間運作的程序(如web伺服器)上不受限制的增長。

用于靜态Web内容的LRU緩存示例:

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'
        
>>> for n in 8, 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)
           

使用緩存實作動态程式設計技術高效計算斐波那契數列的示例:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)
    
>>> [fib(n) for n in range(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)
           

九、類方法 functools.partialmethod(func, *args, **keywords)

版本3.4中新增。
           

傳回一個行為類似partial的新partialmethod描述符,除了它是用于方法定義,而不是直接調用。

func必須是一個descriptor或者可調用對象(兩個對象都像正常函數一樣作為descriptor)。

當func是一個descriptor(比如普遍的Python函數,

classmethod()

staticmethod()

abstractmethod()

,或者其它partialmethod執行個體時,get的調用會委托給底層的descriptor,并傳回一個适當的partial對象。

當func不是可調用的descriptor時,會動态建立一個适當的綁定方法。用于方法時,該行為類似普通的Python函數:self參數會插入為第一個位置參數,甚至在傳遞給partialmethod構造器的args和keywords之前。

示例:

>>> class Cell(object):
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True    
           

十、@functools.singledispatch(default)

版本3.4中新增。
           

将函數轉換為single-dispatch generic函數。

使用@singledispatch裝飾器定義generic函數。注意,dispatch發生在第一個參數的類型上,相應的建立函數:

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print('Let me juset say,', end=' ')
...     print(arg)
           

使用generic函數的

register()

屬性添加函數的重載實作。這是一個裝飾器,接受一個類型參數,并裝飾實作該類型操作的函數:

>>> @fun.register(int)
... def _(arg, verbose=False):
...    if verbose:
...        print('Strength in numbers, eh?', end=' ')
...    print(arg)
...
>>> @fun.register(list)
... def _(arg, verbose=False):
...     if verbose:
...         print('Enumerate this:')
...     for i, elem in enumerate(arg):
...         print(i, elem)
           

為了能夠注冊lambda表達式和預先存在的函數,

register()

可以用于函數形式:

>>> def nothing(arg, verbose=False):
...     print('Nothing.')
...
>>> fun.register(type(None), nothind)
           

register()

屬性傳回未裝飾的函數,可以使用裝飾堆疊,pickling,以及為每個變體單獨建立單元測試:

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print('Half of your number:', end=' ')
...     print(arg / 2)
...
>>> fun_num is fun
False
           

調用時,generic函數根據第一個參數的類型dispatch:

>>> 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 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615
           

當沒有注冊特定類型的實作時,其方法解析順序用于查找更通用的實作。用

@singledispatch

裝飾的原始函數是為object類型注冊的,如果沒有找到更好的實作,則使用它。

使用

dispatch()

屬性檢視generic函數為指定類型選擇哪個實作:

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)
<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>
           

繼續閱讀