functools——高阶函数及其在可调用对象上的操作

资源代码:Lib/functools.py

functools模块是为了高阶函数(该高阶函数的定义为作用于或返回其它函数的函数)而设置的。一般来说,任何可调用的对象在该模块中都可被当做函数而处理。

functools模块定义了以下函数:

functools. cmp_to_key(func)

把老式的比较函数转换为关键字函数。它作为工具被使用,接受关键字函数(例如:sorted()、min()、max()、heapq. nsmallest()、itertools. groupby())。该函数主要作为一个转化工具而存在,它将Python2中支持使用的比较函数转为现行可用的。

比较函数是可以接受两个参数的可调用函数,它对参数进行比较,小于时返回负数、等于时返回零、大于时返回正数。关键字函数是接受单参数的可调用函数,它返回那么应用于排序关键字的其它值。

例如:


sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sortorder

对于排序的例子和一个简短的排序教程,可以参见Sorting HOW TO

 

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

装饰器生成一个可调用的内存来保存最近调用,这个调用不超过maxsize指定的大小,并用其来装饰函数。当需要使用相同的参数周期性的调用I/O绑定函数或高消耗函数时,它可以保存时间。

当一个字典被用于缓存结果时,该函数的位置参数和关键字参数必须具有哈希属性。

如果maxsize被设置为None,LRU特征不可用,缓存的增长没有约束。当maxsize以2为权,LRU的特征表现最佳。

如果typed被设置为True,不同类型的函数参数可以被单独存储。例如,f(3)和f(3.0)被当做不同的调用,产生不同的结果。

为了帮助测量缓存的有效性,以及调整maxsize参数,被装饰的函数会被装入一个cache_info()函数内,并且会返回一个,命名的元组,该院组展示hits、misses、maxsize和currsize。在多线程环境中,hits和missed是相似的。

装饰器也提供一个cache_clear()函数用于清理或无效化缓存。

原始的底层函数可以通过__wrapped__属性访问。这对于自检、略过缓存、用不同的缓存重新装饰函数等操作十分有用。

一个LRU(最近最少被使用的)缓存工作最佳,是出现在最多的调用可以作为预测的最佳语言器(例如,新闻服务商的最流行温江趋向于每日都在改变)。缓存的大小限制确保了缓存不会在长期运行(诸如web服务)中爆表。

静态网页内容的LRU缓存示例:


@lru_cache(maxsize=32)
defget_pep(num):
    'Retrievetext 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 in8, 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)
deffib(n):
    if n <2:
        return n
    return fib(n-1) + fib(n-2)
 
>>> [fib(n) for n inrange(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. total_ordering

给出一个类,该类定义了一个或多个丰富的比较排序方法,这个类装饰器提供了它没有提供的剩余方法。它简化了那些可能涉及到比较丰富比较操作的方法。

该类必须定义了一个__it__()、__le__()、__ge__()或__ge__()。此外,该类应当支持__eq__()方法。

例如:

@total_ordering
classStudent:
    def_is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def__eq__(self, other):
        ifnotself._is_valid_operand(other):
            returnNotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def__lt__(self, other):
        ifnotself._is_valid_operand(other):
            returnNotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))


注意:尽管这个装饰器使得创建表现来那个号的完全排序类型十分简单,但是它会付出更多的执行代价,代码执行的十分缓慢,并且派生的结构更为复杂。如果性能基准测试表明这是给定应用程序的瓶颈,那么实施全部六种丰富的比较方法可能会提供轻松的速度提升。

 

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

返回一个新的partial对象,当它被调用时,其行为类似于func参数在使用位置参数args和关键字参数keywords时被调用的反应一样。如果调用提供了更多的参数,它们将被添加进args中。如果提供了额外的关键字参数,它们扩展并覆盖keywords。大致等价于:

defpartial(func, *args, **keywords):
    defnewfunc(*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()被用于部分函数应用,它将函数参数和/或关键字的某些部分“冻结”,从而产生具有简化签名的新对象,例如partial()可以被用于超早一个可调用者,其行为类似于基参数为默认为2的int()函数:

>>>fromfunctoolsimport partial
>>>basetwo = partial(int, base=2)
>>>basetwo.__doc__='Convert base 2 string to an int.'
>>>basetwo('10010')
18
 
 
class functools. partialmethod(func, *args, **keywords)

返回一个新的partialmethod描述器,它的行为类似于partial(),除了它被设计为一种方法订一起而非被直接调用的对象。

func闭学式一个描述器(就像是一个正常的Python函数,classmethod()、staticmethod()、abstractmethod()或者是partialmethod的其它实例),将调用__get__授予给隐含描述器,并且把适合的partiala独享作为结果返回。

当func是一个非描述器的可调用者,一个适合的限定函数就会被动态生成。该行为类似于普通Python函数在被当做一个方法使用时的行为:self参数将被作为第一个位置参数而插入,甚至在args和keywords被提供给给partialmethod构造器前。

例如:

>>> classCell(object):
 
 
...     def__init__(self):
 
 
...         self._alive=False
 
 
...     @property
 
 
...     defalive(self):
 
 
...         returnself._alive
 
 
...     defset_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. reduce(function, iterable[,initializer])

将具备两个参数的函数function累加到序列iterable的各项中,从左到右,直到把该序列的值削减到只有一个。例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])运行等价于((((1+2)+3)+4)+5)。对于左参数x,它是上次计算出的累加值;对于右参数y,它是从序列中提取出来的新值。如果可选的initializer存在,它被放置在序列的所有计算项目之前,当序列为空时,其即为默认值。如果initializer没有被给出,并且sequence只包含一个项目,那该函数就返回这个项目。

大致等价于:


defreduce(function, iterable, initializer=None):
 
 
it =iter(iterable)if initializer isNone:value =next(it)else:value = initializerfor element in it:value = function(value, element)return value


 

@functools, singledispatch

将一个函数转化为single dispatch generic function(单一调度通用函数)。

定义一个通用函数,用@singledispatch装饰器来装饰它。注意:调度发生在第一个参数的类型上,相应的创造自有函数:

>>> fromfunctoolsimport singledispatch 
 
>>> @singledispatch 
 
... deffun(arg, verbose=False):
 
 
...     if verbose:
 
 
...         print("Let me just say,", end=" ")
 
 
...     print(arg)


使用通用函数的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 inenumerate(arg):
 
 
...         print(i, elem)


为了实现寄存隐藏函数和已存在的函数,register()属性可以以函数的形式被使用:


>>> defnothing(arg, verbose=False):
 
 
...     print("Nothing.")
 
 
...
 
 
>>> fun.register(type(None), nothing)


register()属性返回未装饰的函数,该函数可以被装饰器包装,也可以进行单元测试。

>>> @fun.register(float)
 
 
... @fun.register(Decimal)
 
 
... deffun_num(arg, verbose=False):
 
 
...     if verbose:
 
 
...         print("Half of your number:", end=" ")
 
 
...     print(arg/2)
 
 
...
 
 
>>> fun_numis 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 spam1 spam2 eggs3 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)

更新包装器函数,使其看起来更像一个被包装的函数。可选参数是元组,其指定原始函数的那些属性可以直接匹配包装器函数上的对应属性。并且,包装器的属性在其余原始函数的属性相匹配时更新。这些参数的默认值是模块级常量:WAPPER_ASSIGNMENTS(其被赋给包装器函数的__module__、__name__、__qualname__、__annotations__和__doc__,其为文档字符串)并且WAPPER_UPDATES(更新包装器函数的__dict__,为实例字典)。

为了自检或获取其它支持(即通过类似于iru_cache()绕过装饰器缓存)需要允许获取原始函数,这些函数自动的添加__wrapped__属性参考那些已经被包装的函数来包装。

这个函数的主要用途是在装饰函数中,它封装了装饰函数并返回包装器。如果包装器函数未被更新,返回函数的元数据将反映包装器定义而不是原始函数定义,这通常不太有用。

update_wrapper()可能被用于调用其他函数。任何被指派或更新的属性都会被忽略。AttributeError会被抛出,当包装器函数忽略了任何命名为Updated的属性。

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

这是一个简化函数,可以在定义一个包装器函数时调用update_wrapper()作为函数装饰器。它等价于partial(update_wrapper, wrapped=wrapped, assigned=assigned,updated=updated)。例如:

>>> fromfunctoolsimport wraps
 
 
>>> defmy_decorator(f):
 
 
...     @wraps(f)
 
 
...     defwrapper(*args,**kwds):
 
 
...         print('Calling decorated function')
 
 
...         return f(*args,**kwds)
 
 
...     return wrapper
 
 
...
 
 
>>> @my_decorator
 
 
... defexample():
 
 
...     """Docstring"""
 
 
...     print('Called example function')
 
 
...
 
 
>>> example()
 
 
Calling decorated functionCalled example function 
 
>>> example.__name__
 
 
'example' 
 
>>> example.__doc__
 
 
'Docstring'


如果不使用这个装饰器工具,示例函数的名称将是“包装器”,而原始示例()的DoScript将丢失。

 

partial对象

    partial对象是可调用的独享,其由partial()创建。它们有三个只读属性:

partial. func

函数的一个可调用对象。调用partial对象将给func转发新的参数和关键字。

partial. args

最左端的位置参数将被最先考虑提供给partial对象调用。

partial. keywords

当partial对象被调用时,关键字参数就会被提供。

 

partial对象类似于function对象,它们都可以被调用、弱可引,且都具有属性。这里有许多关键性的差别。例如,__name__和__doc__属性不会自动的创建。并且,类中定义的partial对象的行为类似于静态方法,并且在实例属性查找期间内部转化为绑定方法。