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()函數返回一個命名元組,包括hitsmissesmaxsizecurrsize。在多線程環境中,hitsmisses是近似值。

裝飾器還提供了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構造器的argskeywords之前。

示例:

>>> 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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章