您的位置:  首页 » Python » functools-可调用对象的高阶函数和操作详解(2)Python语言的功能编程模块(必读进阶学习教程)(参考资料)

functools模块用于高阶函数:作用于或返回其他函数的函数。通常,任何可调用对象都可以被视为用于此模块的功能。

functools模块定义了以下功能:

functools.cmp_to_keyfunc 
将旧式比较功能转换为关键功能。使用接受钥匙功能的工具(如sorted()min(), max()heapq.nlargest()heapq.nsmallest(), itertools.groupby())。此函数主要用作从Python 2转换的程序的转换工具,它支持使用比较函数。

比较函数是任何可调用的,它接受两个参数,比较它们,并返回负数小于,零表示相等,或正数表示大于。键函数是一个可调用的函数,它接受一个参数并返回另一个值作为排序键。

例:

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

 

有关排序示例和简要排序教程,请参阅排序方式

版本3.2中的新功能。

@functools.lru_cachemaxsize = 128typed = False 
Decorator用一个memoizing callable来包装一个函数,它可以节省 maxsize最近的调用。当使用相同的参数定期调用昂贵的或I / O绑定函数时,它可以节省时间。

由于字典用于缓存结果,因此函数的位置和关键字参数必须是可清除的。

不同的参数模式可以被认为是具有单独的高速缓存条目的不同调用。例如,f(a = 1,b = 2)f(b = 2,a = 1) 的关键字参数顺序不同,并且可能有两个单独的缓存条目。

如果将maxsize设置为None,则禁用LRU功能,并且缓存可以无限制地增长。当maxsize是2的幂时,LRU功能表现最佳。

如果typed设置为true,则将分别缓存不同类型的函数参数。例如,f(3)并且f(3.0)将被视为产生截然不同的结果不同的呼叫。

为了帮助测量缓存的有效性并调整maxsize 参数,包装函数使用一个cache_info() 函数进行检测,该函数返回一个命名元组,显示hitsmisses, maxsizecurrsize。在多线程环境中,命中和未命中是近似值。

装饰器还提供cache_clear()清除或使缓存无效的功能。

可以通过__wrapped__属性访问原始基础函数 。这对于内省,绕过缓存或使用不同的缓存重新包装函数很有用。

一个LRU(最近最少使用)缓存效果最好的最近通话是即将到来的呼叫的最佳预测(例如,新闻服务器上的最流行的文章往往每天更换)。缓存的大小限制可确保缓存不会增长而不受长时间运行的进程(如Web服务器)的限制。

通常,只有在要重用以前计算的值时才应使用LRU高速缓存。因此,缓存具有副作用的函数,需要在每次调用时创建不同的可变对象的函数或者诸如time()或random()之类的不纯函数是没有意义的。

静态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)

 

使用高速缓存实现 动态编程 技术有效计算Fibonacci数的示例 :

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

 

版本3.2中的新功能。

版本3.3中已更改:添加了键入的选项。

@functools.total_ordering
给定一个定义一个或多个丰富的比较排序方法的类,这个类装饰器提供其余的。这简化了指定所有可能的丰富比较操作所涉及的工作:

这个类必须定义之一__lt__()__le__(), __gt__(),或__ge__()。此外,该课程应该提供一种__eq__()方法。

例如:

@total_ordering
class Student:
    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()))

 

注意

虽然这个装饰器可以很容易地创建表现良好的完全有序的类型,但它确实以较慢的执行成本和更复杂的派生比较方法的堆栈跟踪为代价。如果性能基准测试表明这是给定应用程序的瓶颈,那么实现所有六种丰富的比较方法可能会提供简单的速度提升。

版本3.2中的新功能。

版本3.4中已更改:现在支持从无法识别的类型的基础比较函数返回NotImplemented。

functools.partialfunc* args**关键字
返回一个新的部分对象,当被调用时,其行为类似于使用位置参数args 和关键字参数关键字调用的func。如果为调用提供了更多参数,则将它们附加到args。如果提供了其他关键字参数,则它们会扩展和覆盖关键字。大致相当于:

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()用于局部功能应用其中“冻结”的函数的参数和/或产生具有简化签名的新对象的关键字一些部分。例如,partial()可以用于创建一个callable,其行为类似于base参数默认为两个的int()函数:

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

 

class functools.partialmethodfunc* args** keywords 
返回一个新的partialmethod描述符,其行为类似于partial它被设计为用作方法定义而不是可直接调用。

func必须是描述符或可调用对象(与正常函数一样,对象都作为描述符处理)。

FUNC是一个描述符(例如正常Python函数, classmethod()staticmethod()abstractmethod()或的另一个实例partialmethod),则调用__get__委派到底层描述符,和一个适当的 部分对象作为结果返回。

func是非描述符可调用时,动态创建适当的绑定方法。当用作方法时,它的行为类似于普通的Python函数:self参数将作为第一个位置参数插入,甚至在提供给构造函数的args关键字之前partialmethod

例:

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

 

版本3.4中的新功能。

functools.reducefunctioniterable [initializer 
应用功能的两个参数来累计的项目序列,从左至右,这样的顺序,以减少单个值。例如, 计算。左参数x是累加值,右参数y序列的更新值。如果存在可选的初始值设定项,则它将位于计算中序列的项之前,并在序列为空时用作默认值。如果未给出初始化程序且 序列仅包含一个项目,则返回第一个项目。reduce(lambda x,y: x+y, [1, 2, 3, 4, 5])((((1+2)+3)+4)+5)

大致相当于:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

 

@functools.singledispatch
将函数转换为单调度 泛型函数

要定义泛型函数,请使用@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)
...

 

要启用注册lambdas和预先存在的函数,register()可以以函数形式使用该 属性:

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

 

register()属性返回未修饰的函数,该函数启用装饰器堆叠,酸洗,以及为每个变量独立创建单元测试:

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

 

版本3.4中的新功能。

版本3.7中已更改:register()属性支持使用类型注释。

functools.update_wrapper包装包装分配= WRAPPER_ASSIGNMENTS更新= WRAPPER_UPDATES 
更新包装函数以使其看起来像包装函数。可选参数是元组,用于指定原始函数的哪些属性直接分配给包装函数上的匹配属性,以及使用原始函数中的相应属性更新包装函数的哪些属性。这些参数的默认值是模块级的常量WRAPPER_ASSIGNMENTS(其中分配给包装函数的__module____name____qualname____annotations__ 和__doc__,文档字符串)和WRAPPER_UPDATES(其更新的包装函数的__dict__,即实例字典)。

为了允许访问原始函数以进行内省和其他目的(例如绕过缓存装饰器等lru_cache()),此函数会自动__wrapped__向包装器添加一个属性,该属性引用被包装的函数。

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

update_wrapper()可以与函数以外的可调用项一起使用。将忽略被包装对象中缺少的分配更新的任何属性(即此函数不会尝试在包装函数上设置它们)。AttributeError如果包装函数本身缺少更新中指定的任何属性,则仍会引发。

版本3.2中的新功能:自动添加__wrapped__属性。

版本3.2中的新功能:__annotations__默认情况下复制属性。

版本3.2中已更改:缺少属性不再触发AttributeError

改变在3.4版本:__wrapped__属性现在始终是指包装的函数,即使该函数定义的__wrapped__属性。(见bpo-17482

@functools.wraps包装,已分配= WRAPPER_ASSIGNMENTS更新= WRAPPER_UPDATES 
这是一个便利函数,用于update_wrapper()在定义包装函数时调用函数装饰器。它相当于 。例如:partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

>>>
>>> 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() 将丢失。

partial对象

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

partial.func
可调用的对象或函数。partial对象的调用将转发到func新的参数和关键字。
partial.args
最左侧的位置参数将添加到提供给partial对象调用的位置参数之前。
partial.keywords
partial调用对象时将提供的关键字参数。

partial对象就像function对象,因为它们是可调用的,弱引用的,并且可以具有属性。有一些重要的区别。例如,不会自动创建__name____doc__属性。此外,partial类中定义的对象的行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法。