目录
谓高阶函数,就是一个函数接收另一个函数作为参数,如内置函数map()、filter()、min()、max()、sorted()等均为高阶函数。
在模块 functools 里有很多实用的高阶函数和装饰器,本文将讲解 functools 模块中较常用的累计函数 reduce()、偏函数 partial()和装饰器 @wraps。
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
偏函数是对指定函数的二次包装,通常是将现有函数的部分参数预先绑定,从而得到一个新的函数,该函数就称为偏函数。相比原函数,偏函数具有较少的可变参数,降低了函数调用难度。 偏函数会 “冻结” 一部分函数参数,从而得到一个具有简化操作的新对象。
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'
使用装饰器包装函数后,被包装的函数属性无法被读取,可以通过@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'>}