python模塊之functools

functools模塊提供了某些高階函數(high-order function)。python

functools.cmp_to_key(func)

比較函數是接收兩個參數進行比較的函數,返回一個負數表示<,返回0表示=,返回一個正數表示>。key函數接收一個參數並返回另外一個值做爲進行排序的鍵。
將比較函數轉換爲key函數,常在用到key關鍵字的函數如sotred(), min(), heapq.nlargest(), itertools,groupby()中使用。cmp_to_key主要做爲過渡工具將python2中支持的比較函數進行轉換。緩存

def numeric_compare(x, y):
    return x - y

# python2
print(sorted([5, 2, 4, 1, 3], cmp=numeric_compare))

# python3
print(sorted([5, 2, 4, 1, 3], key=cmp_to_key(numeric_compare)))

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

使用memoize包裝函數,保存最近maxsize次的函數調用結果。在I/O-bound函數上裝飾,處理相同參數時能夠節省函數執行時間。app

  • 若是maxsize爲None,會禁用LRU緩存特性(並不是禁用緩存)且緩存大小會無限增加。maxsize設置爲2的n次方時性能最優。
  • 若是typed爲True,不一樣類型函數參數的執行結果會被分別緩存,例如f(3)f(3.0)會被視爲有兩個不一樣結果的不一樣調用。

由於該裝飾器使用字典緩存函數執行結果,因此函數的位置參數和關鍵字參數必須是可哈希的。dom

不一樣的參數模式可能會被視爲不一樣的緩存實體。例如f(a=1, b=2)f(b=2, a=1)雖然只是關鍵字順序不一樣但可能有兩個單獨的緩存實體。函數

被包裝的函數能夠調用cache_info(),它返回一個(hits, misses, maxsize, currsize)形式的命名元組;能夠調用cache_clear()清空或使緩存無效;還能夠調用__wrapped__屬性繞過緩存,訪問原始的底層函數。工具

LRU(least recently used)緩存一般應僅用在須要重用先前計算的值的場景,其餘場景如使用LRU有不良影響、每次調用須要返回不一樣結果、time()、random()等應禁止使用。maxsize表示緩存大小限制,確保不會無限制增加。性能

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

@functools.total_ordering

類裝飾器,包裝在定義了一個或多個富比較排序方法的類上,會補足其餘的比較方法。
該類必須定義__lt__(), __le__(), __gt__(), 或__ge__()中至少一個方法,並建議定義__eq__()方法。單元測試

@total_ordering
class Student:
    def __init__(self, lastname, firstname):
        self.lastname = lastname
        self.firstname = firstname

    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))

    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))

    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

NOTE:使用這個裝飾器的代價是,裝飾器自動補足的比較方法耗費了更多的執行時間並擁有更復雜的堆棧跟蹤。若是應用性能基準測試代表是使用此裝飾器形成的瓶頸,手動實現全部六種富比較方法能夠帶來直觀的速度提高。測試

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

偏函數,返回一個partial對象,調用時等同調用了使用預設位置參數和關鍵字參數的func函數。若是partial對象調用時又提供了額外的位置參數,追加到args,若是提供了額外的關鍵字參數,擴展keywords並覆蓋相同的鍵值。this

大體等同於:

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()用於"凍結"函數的部分位置參數和/或關鍵字參數而產生一個表明某部分函數功能的簡化標誌。

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

class functools.partialmethod(func, *args, **keywords)

後續補充

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

將可迭代對象iterable中的元素從左到右累計到接收兩個參數的函數func中。例如reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])的計算等同於((((1+2)+3)+4)+5)
若是設置了可選參數initializer,它被放置在要計算的序列以前,並在序列爲空時做爲默認值;若是未提供initializer的值且序列僅包含一個元素,返回該元素。

@functools.singledispatch

將函數轉換爲單分派泛函數(single-dispatch generic function)。

使用@singledispatch裝飾器定義一個泛函數,單分派僅限於第一個參數的類型:

from functools import singledispatch

@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

使用泛函數的register()屬性添加劇載實現,該屬性是一個裝飾器。對於使用類型註解的函數,該裝飾器將自動推斷第一個參數的類型:

@fun.register
def _(arg: int, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register
def _(arg: list, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

若是不使用類型註解,能夠顯式地給裝飾器傳遞類型參數:

@fun.register(complex)
def _(arg, verbose=False):
    if verbose:
        print("Better than complicated.", end=" ")
    print(arg.real, arg.imag)

也能夠以函數的形式使用register()註冊lambda函數和已定義的函數:

def nothing(arg, verbose=False):
    print("Nothing.")

fun.register(type(None), nothing)

register()屬性返回容許裝飾器堆疊、序列化以及建立獨立的單元測試的未裝飾的函數:

from decimal import Decimal

@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

調用時,泛函數只分派第一個參數的類型:

>>> 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()屬性查看泛函數使用了哪一個實現:

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

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

更新包裝函數(wrapper),使其看起來更像被包裝的原始函數(wrapped)。元組類型的可選參數assigned指定wrapped函數的哪些屬性直接分派到wrapper函數對應的屬性上,updated參數指定使用wrapped函數的哪些屬性更新wrapper函數對應的屬性。

WRAPPER_ASSIGNMENTS的默認值是'__module__', '__name__', '__qualname__', '__doc__','__annotations__'
WRAPPER_UPDATES的默認值是'__dict__'

經過訪問包裝函數的__wrapped__屬性能夠獲取到被包裝的原始函數。

該函數主要用於裝飾器使用場景下,若是不更新包裝函數,返回函數的元數據將指向包裝函數而非被包裝的原始函數,通常來講沒太大意義。

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """this is a wrapper function"""
        return f(*args, **kwargs)
    # update_wrapper(wrapper_function, f)
    return wrapper_function

@wrapper
def wrapped():
    """this is a wrapped function"""
    pass

print(wrapped.__doc__)
print(wrapped.__name__)
# 不使用update_wrapper的結果:
>>> this is a wrapper function
>>> wrapper_function

# 使用update_wrapper的結果:
>>> this is a wrapped function
>>> wrapped

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

等同於partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated),不過是做爲包裝函數定義時的裝飾器使用。

def wrapper(f):
    @wraps(f)
    def wrapper_function(*args, **kwargs):
        """this is a wrapper function"""
        return f(*args, **kwargs)
    return wrapper_function

@wrapper
def wrapped():
    """this is a wrapped function"""
    pass

print(wrapped.__doc__)
print(wrapped.__name__)

partial對象

partial對象是由partial()方法建立的可調用對象,它有三個只讀屬性:
partial.func
一個可調用對象或函數。調用partial對象將使用新的位置參數和關鍵字參數轉發到func。
partial.args
調用partial()時提供的位置參數
partial.keywords調用partial()時提供的關鍵字參數

相關文章
相關標籤/搜索