原文:https://zhuanlan.zhihu.com/p/131634488
一、简介
functools,用于高阶函数:指那些作用于函数或者返回其它函数的函数,通常只要是可以被当做函数调用的对象就是这个模块的目标。
在Python 2.7 中具备如下方法,
cmp_to_key,将一个比较函数转换关键字函数;(Python 3 不支持)
partial,针对函数起作用,并且是部分的;
reduce,与python内置的reduce函数功能一样;
total_ordering,在类装饰器中按照缺失顺序,填充方法;
update_wrapper,更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数;
wraps,可用作一个装饰器,简化调用update_wrapper的过程;
二、方法
1、cmp_to_key
functools.cmp_to_key(func)
将老式鼻尖函数转换成key函数,用在接受key函数的方法中(such as sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby())
一个比较函数,接收两个参数,小于,返回负数,等于,返回0,大于返回整数
key函数,接收一个参数,返回一个表明该参数在期望序列中的位置
2、partial
functools.partial用于部分应用一个函数,它基于一个函数创建一个可调用对象,把原函数的某些参数固定,调用时只需要传递未固定的参数即可。
import functools
def add(a, b):
print("当前结果值", a+b)
add = functools.partial(add, 1)
add(2)
# 输出
当前结果值3
add函数原本接收两个参数a和b,经过partial包装之后,a参数的值被固定为了1,新的add对象(注意此处add已经是一个可调用对象,而非函数,下文分析源码会看到)只需要接收一个参数即可。
通俗点说:就是把原函数的部分参数固定了初始值,新的调用只需要传递其它参数。
下面来分析partial的源码(Python3.7),只摘录了核心部分:
class partial:
"""New function with partial application of the given arguments
and keywords.
"""
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
def __new__(*args, **keywords):
if not args:
raise TypeError("descriptor '__new__' of partial needs an argument")
if len(args) < 2:
raise TypeError("type 'partial' takes at least one argument")
cls, func, *args = args
if not callable(func):
raise TypeError("the first argument must be callable")
args = tuple(args)
if hasattr(func, "func"):
args = func.args + args
tmpkw = func.keywords.copy()
tmpkw.update(keywords)
keywords = tmpkw
del tmpkw
func = func.func
self = super(partial, cls).__new__(cls)
self.func = func
self.args = args
self.keywords = keywords
return self
def __call__(*args, **keywords):
if not args:
raise TypeError("descriptor '__call__' of partial needs an argument")
self, *args = args
newkeywords = self.keywords.copy()
newkeywords.update(keywords)
return self.func(*self.args, *args, **newkeywords)
通过重写“_new__”方法,自定义对象实例化过程。
1、元组拆包,获取到传入的原函数(func)和需要固定的参数(args)
cls, func, *args = args
2、主要是为了支持嵌套调用,即add=partial(partial(add,1),2)这种情况,可先看第三步,回过头再来看
if hasattr(func, "func"):
args = func.args + args
tmpkw = func.keywords.copy()
tmpkw.update(keywords)
keywords = tmpkw
del tmpkw
func = func.func
3、实例化partial对象,将传入的函数和参数设置为当前对象的属性
self = super(partial, cls).__new__(cls)
self.func = func
self.args = args
self.keywords = keywords
return self
到这里我们已经明白了partial是怎么保存原函数和固定参数的了,下面来看一下调用的时候是如何执行的。
先简单了解一下可调用对象:当一个类实现了"__call__"方法后,这个类的对象就能够像函数一样被调用。
class Callable:
def __call__(self, a, b):
return a + b
func = Callable()
result = func(2, 3) # 像函数一样调用
print(result)
输出:5
好啦,我们看下partial的"__call__"是如何实现的:
def __call__(*args, **keywords):
if not args:
raise TypeError("descriptor '__call__' of partial needs an argument")
self, *args = args
newkeywords = self.keywords.copy()
newkeywords.update(keywords)
return self.func(*self.args, *args, **newkeywords)
1、元组拆包,获取到传入的非固定参数args
self, *args = args
2、拷贝当前对象的keywords参数,并且合并传入的非固定参数字典
newkeywords = self.keywords.copy()
newkeywords.update(keywords)
3、调用当前对象的func属性,func即被partial包装的原函数,同时传入暂存的固定参数self.args以及新传入的其它参数。
至此一切真相大白:partial通过实现"__new__"和"__call__"生成一个可调用对象,这个对象内部保存了被包装函数以及固定参数,这个对象可以像函数一样被调用,调用时,其实是执行了对象内部持有的被包装函数,其参数由固定参数和新传入的参数组合而来。、
3、reduce
与Python内置的reduce函数一样,为了向Python3过渡;
4、total_ordering
这个装饰器是在python2.7的时候加上的,它是针对某个类如果定义了lt、le、gt、ge这些方法中的至少一个,使用该装饰器,则会自动的把其他几个比较函数也实现在该类中
from functools import total_ordering
class Person:
# 定义相等的比较函数
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()))
p1 = Person()
p2 = Person()
p1.lastname = "123"
p1.firstname = "000"
p2.lastname = "1231"
p2.firstname = "000"
print p1 < p2 # True
print p1 <= p2 # True
print p1 == p2 # False
print p1 > p2 # False
print p1 >= p2 # False
5、update_wrapper
更新一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数。
下面来看update_wrapper的源码:
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
# from the wrapped function when updating __dict__
wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
代码很简洁,就是把wrapped函数的属性拷贝到wrapper函数中。
wrapped是被装饰的原函数
wrapper是被装饰器装饰后的新函数。
6、wraps
这个函数可用作一个装饰器,简化调用update_wrapper的过程,调用这个函数等价于调用partial(update_wrapper, wrapped = wrapped, assigned = assigned,updated = updated)。
下面来看update_wrapper的源码:
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wrapped:指被装饰器装饰的原函数,我们的装饰器便是要拷贝它的属性。
assigned:要被重新赋值的属性列表,默认为WRAPPER_ASSIGNMENTS,可自定义传入
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
updated:要被合并的属性列表,默认为WRAPPER_UPDATES,可自定义传入
WRAPPER_UPDATES = ('__dict__',)
返回值:
返回了一个partial对象,这个对象对update_wrapper进行了包装,固定了wrapped,assigned,updated三个参数。
wraps本省就是一个装饰器,因为它返回的是一个“函数”即partial对象,这个对象接收函数作为参数,同时以函数作为返回值。