目录
1. 高阶函数基础
谓高阶函数,就是一个函数接收另一个函数作为参数,如内置函数map()、filter()、min()、max()、sorted()等均为高阶函数。
在模块 functools 里有很多实用的高阶函数和装饰器,本文将讲解 functools 模块中较常用的累计函数 reduce()、偏函数 partial()和装饰器 @wraps。
- reduce() :将可迭代对象元素作为参数依次应用到函数中,常用于求和或求积;
- partial():用于对指定函数进行“二次”包装,是闭包的简易实现;
- @wraps:用于对装饰器进行装饰,方便获取被包装的函数属性。
2. 累计函数:reduce()
reduce() 函数格式如下:
functools.reduce(function, iterable[, initializer])
将 iterable 可迭代对象中的元素作为参数依次应用到 function 函数中,最终返回累计的结果。
如果存在 initializer 参数,它将被放在 iterable 的元素之前,并在 iterable 参数为空时作为默认值使用;如果没有指定 initializer 参数,并且 iterable 只有一个元素,则返回该元素的值。
实现原理大致等价于:
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
reduce() 只支持有两个参数的函数作为参数输入,输入参数小于或大于2的函数都报错。
#输入带有三个参数的函数,报错
def add3(x,y,z):
... return x+y+z
...
import functools
functools.reduce(add3,[1,2,3,4])
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: add3() missing 1 required positional argument: 'z'
#输入只有一个参数的函数,报错
def fun1(x):
... return x*x
...
functools.reduce(fun1,[1,2,3,4])
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: fun1() takes 1 positional argument but 2 were given
initializer 为空时,以序列的第一个元素作为输入函数的参数1,第二个元素作为参数2,并第一次执行输入函数;随后以第一次执行结果为参数1,第三个元素为参数2,第二次执行输入函数;循环往复,直到序列元素使用完毕;
initializer 不为空时,则以 initializer 为输入函数的参数1,序列的第一个元素为参数2,第一次执行输入函数,后续则与前述循环一致;可视为序列新增 initializer值 为第一个元素,再将 initializer 置为空。
#作为参数的函数
def calculate(var1,var2):
... return (var2 - var1) * var1
...
import functools
#initializer 为空时,执行 reduce() 函数
functools.reduce(calculate,[2,1,5])
-14
#实际运算过程
(5 - ((1 - 2) * 2)) * ((1 - 2) * 2)
-14
#initializer 为0时,执行 reduce() 函数
functools.reduce(calculate,[2,1],0)
0
#实际运算过程
(1 - ((2 - 0) * 0)) * ((2 - 0) * 0)
0
#initializer 为3,以及直接将 initializer 值作为序列的第一个元素,结果一致
functools.reduce(calculate,[3,2,1])
-12
functools.reduce(calculate,[2,1],3)
-12
#实际运算过程
(1 - ((2 - 3) * 3)) * ((2 - 3) * 3)
-12
3. 偏函数:partial()
偏函数是对指定函数的二次包装,通常是将现有函数的部分参数预先绑定,从而得到一个新的函数,该函数就称为偏函数。相比原函数,偏函数具有较少的可变参数,降低了函数调用难度。 偏函数会 “冻结” 一部分函数参数,从而得到一个具有简化操作的新对象。
partial() 函数格式如下:
functools.partial(func, /, *args, **keywords)
partial() 函数返回一个偏函数,当被调用时,其行为类似于 func 函数附带位置参数 args 和关键字参数 keywords 被调用。
偏函数被调用的时候,如果提供更多的参数,它们会被附加到 func 函数的位置参数 args 中;如果额外的关键字参数,它们会扩展并重载 func 函数的关键字参数 keywords。
实现原理大致等价于:
def partial(func, /, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = {**keywords, **fkeywords}
return func(*args, *fargs, **newkeywords)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
已被 partial() 录入的位置参数、关键字参数,不可被生成的偏函数重复录入,否则报错;
实战示例如下:
#作为参数输入的函数
def functest(var1,var2,var3,var4):
... return var1 + var2 + var3 + var4
...
#partial() 生成一个已定义位置参数1、2,和关键字参数var4=5的,functest函数的偏参数s
s = functools.partial(functest,1,2,var4=5)
#偏函数 s 输入位置参数3,执行成功
s(3)
11
#实际运算过程
1 + 2 + 3 + 5
11
#偏函数 s 输入关键字参数var3=6,执行成功
s(var3=6)
14
#实际运算过程
1 + 2 + 6 + 5
14
#输入过多、过少的位置参数,报错
s(3,4)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: functest() got multiple values for argument 'var4'
s()
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: functest() missing 1 required positional argument: 'var3'
#输入已被赋值的关键字参数,报错
s(var2=2,var3=6)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: functest() got multiple values for argument 'var2'
4. 函数装饰器:@wraps
使用装饰器包装函数后,被包装的函数属性无法被读取,可以通过@wraps将被包装函数的属性赋予装饰器,方便后续自省操作。
@wraps 函数格式如下:
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
这是一个便捷函数,用于在定义包装函数时发起调用 update_wrapper() 作为函数装饰器。它等价于 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)。
update_wrapper 函数格式如下:
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
更新一个包装函数以使其看起来像是被包装的函数,可选参数是元组,用于指定原始函数的哪些属性直接分配给包装函数上的对应属性,以及使用原始函数中的相应属性更新包装函数的哪些属性。这些参数的默认值是模块级的常量 WRAPPER_ASSIGNMENTS(其中分配给包装函数的 __module__、__name__、__qualname__、__annotations__ 和 __doc__)和 WRAPPER_UPDATES(其更新的包装函数的 __dict__,即实例字典)。
为了允许访问原始函数以进行内省和其他目的(例如绕过缓存装饰器等 lru_cache()),此函数会自动 __wrapped__ 向包装器添加一个属性,该属性引用被包装的函数。
未使用@wraps,读取装饰器各属性代码如下:
def log(funx):
def closure_test(var1,var2):
print(f'开始函数{funx.__name__}调用!')
funx(var1,var2)
print(f'结束函数{funx.__name__}调用!')
return closure_test
@log
def functiontest(var1: str, var2: list[int]) -> str:
"""
函数测试
:param var1: 初始字符串
:param var2: 整型列表
:return: 整合后的列表
"""
for i in var2:
var1 += str(i)
print(f'最终结果为{var1}')
return var1
functiontest('1',(2,3,4,5))
print(functiontest.__name__)
print(functiontest.__doc__)
print(functiontest.__annotations__)
结果为返回闭包函数属性:
开始函数functiontest调用!
最终结果为12345
结束函数functiontest调用!
closure_test
None
{}
使用@wraps,读取装饰器各属性代码如下:
import functools
def log(funx):
@functools.wraps(funx)
def closure_test(var1,var2):
print(f'开始函数{funx.__name__}调用!')
funx(var1,var2)
print(f'结束函数{funx.__name__}调用!')
return closure_test
@log
def functiontest(var1: str, var2: list[int]) -> str:
"""
函数测试
:param var1: 初始字符串
:param var2: 整型列表
:return: 整合后的列表
"""
for i in var2:
var1 += str(i)
print(f'最终结果为{var1}')
return var1
functiontest('1',(2,3,4,5))
print(functiontest.__name__)
print(functiontest.__doc__)
print(functiontest.__annotations__)
结果为返回被装饰函数属性:
开始函数functiontest调用!
最终结果为12345
结束函数functiontest调用!
functiontest
函数测试
:param var1: 初始字符串
:param var2: 整型列表
:return: 整合后的列表
{'var1': <class 'str'>, 'var2': list[int], 'return': <class 'str'>}