• 函数8:高阶函数


    目录

    1. 高阶函数基础

    2. 累计函数:reduce()

    3. 偏函数:partial()

    4. 函数装饰器:@wraps


    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 只有一个元素,则返回该元素的值。 

    实现原理大致等价于:  

    1. def reduce(function, iterable, initializer=None):
    2. it = iter(iterable)
    3. if initializer is None:
    4. value = next(it)
    5. else:
    6. value = initializer
    7. for element in it:
    8. value = function(value, element)
    9. return value

    reduce() 只支持有两个参数的函数作为参数输入,输入参数小于或大于2的函数都报错。

    1. #输入带有三个参数的函数,报错
    2. def add3(x,y,z):
    3. ... return x+y+z
    4. ...
    5. import functools
    6. functools.reduce(add3,[1,2,3,4])
    7. Traceback (most recent call last):
    8. File "<input>", line 1, in <module>
    9. TypeError: add3() missing 1 required positional argument: 'z'
    10. #输入只有一个参数的函数,报错
    11. def fun1(x):
    12. ... return x*x
    13. ...
    14. functools.reduce(fun1,[1,2,3,4])
    15. Traceback (most recent call last):
    16. File "<input>", line 1, in <module>
    17. TypeError: fun1() takes 1 positional argument but 2 were given

    initializer 为空时,以序列的第一个元素作为输入函数的参数1,第二个元素作为参数2,并第一次执行输入函数;随后以第一次执行结果为参数1,第三个元素为参数2,第二次执行输入函数;循环往复,直到序列元素使用完毕;

    initializer 不为空时,则以 initializer 为输入函数的参数1,序列的第一个元素为参数2,第一次执行输入函数,后续则与前述循环一致;可视为序列新增 initializer值 为第一个元素,再将 initializer 置为空。

    1. #作为参数的函数
    2. def calculate(var1,var2):
    3. ... return (var2 - var1) * var1
    4. ...
    5. import functools
    6. #initializer 为空时,执行 reduce() 函数
    7. functools.reduce(calculate,[2,1,5])
    8. -14
    9. #实际运算过程
    10. (5 - ((1 - 2) * 2)) * ((1 - 2) * 2)
    11. -14
    12. #initializer 为0时,执行 reduce() 函数
    13. functools.reduce(calculate,[2,1],0)
    14. 0
    15. #实际运算过程
    16. (1 - ((2 - 0) * 0)) * ((2 - 0) * 0)
    17. 0
    18. #initializer 为3,以及直接将 initializer 值作为序列的第一个元素,结果一致
    19. functools.reduce(calculate,[3,2,1])
    20. -12
    21. functools.reduce(calculate,[2,1],3)
    22. -12
    23. #实际运算过程
    24. (1 - ((2 - 3) * 3)) * ((2 - 3) * 3)
    25. -12

    3. 偏函数:partial()

    偏函数是对指定函数的二次包装,通常是将现有函数的部分参数预先绑定,从而得到一个新的函数,该函数就称为偏函数。相比原函数,偏函数具有较少的可变参数,降低了函数调用难度。 偏函数会 “冻结” 一部分函数参数,从而得到一个具有简化操作的新对象。

    partial() 函数格式如下:

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

    partial() 函数返回一个偏函数,当被调用时,其行为类似于 func 函数附带位置参数 args 和关键字参数 keywords 被调用。  

    偏函数被调用的时候,如果提供更多的参数,它们会被附加到 func 函数的位置参数 args 中;如果额外的关键字参数,它们会扩展并重载 func 函数的关键字参数 keywords。  

    实现原理大致等价于:  

    1. def partial(func, /, *args, **keywords):
    2. def newfunc(*fargs, **fkeywords):
    3. newkeywords = {**keywords, **fkeywords}
    4. return func(*args, *fargs, **newkeywords)
    5. newfunc.func = func
    6. newfunc.args = args
    7. newfunc.keywords = keywords
    8. return newfunc

    已被 partial() 录入的位置参数、关键字参数,不可被生成的偏函数重复录入,否则报错;

    实战示例如下:

    1. #作为参数输入的函数
    2. def functest(var1,var2,var3,var4):
    3. ... return var1 + var2 + var3 + var4
    4. ...
    5. #partial() 生成一个已定义位置参数1、2,和关键字参数var4=5的,functest函数的偏参数s
    6. s = functools.partial(functest,1,2,var4=5)
    7. #偏函数 s 输入位置参数3,执行成功
    8. s(3)
    9. 11
    10. #实际运算过程
    11. 1 + 2 + 3 + 5
    12. 11
    13. #偏函数 s 输入关键字参数var3=6,执行成功
    14. s(var3=6)
    15. 14
    16. #实际运算过程
    17. 1 + 2 + 6 + 5
    18. 14
    19. #输入过多、过少的位置参数,报错
    20. s(3,4)
    21. Traceback (most recent call last):
    22. File "<input>", line 1, in <module>
    23. TypeError: functest() got multiple values for argument 'var4'
    24. s()
    25. Traceback (most recent call last):
    26. File "<input>", line 1, in <module>
    27. TypeError: functest() missing 1 required positional argument: 'var3'
    28. #输入已被赋值的关键字参数,报错
    29. s(var2=2,var3=6)
    30. Traceback (most recent call last):
    31. File "<input>", line 1, in <module>
    32. 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,读取装饰器各属性代码如下:

    1. def log(funx):
    2. def closure_test(var1,var2):
    3. print(f'开始函数{funx.__name__}调用!')
    4. funx(var1,var2)
    5. print(f'结束函数{funx.__name__}调用!')
    6. return closure_test
    7. @log
    8. def functiontest(var1: str, var2: list[int]) -> str:
    9. """
    10. 函数测试
    11. :param var1: 初始字符串
    12. :param var2: 整型列表
    13. :return: 整合后的列表
    14. """
    15. for i in var2:
    16. var1 += str(i)
    17. print(f'最终结果为{var1}')
    18. return var1
    19. functiontest('1',(2,3,4,5))
    20. print(functiontest.__name__)
    21. print(functiontest.__doc__)
    22. print(functiontest.__annotations__)

    结果为返回闭包函数属性:

    1. 开始函数functiontest调用!
    2. 最终结果为12345
    3. 结束函数functiontest调用!
    4. closure_test
    5. None
    6. {}

    使用@wraps,读取装饰器各属性代码如下:

    1. import functools
    2. def log(funx):
    3. @functools.wraps(funx)
    4. def closure_test(var1,var2):
    5. print(f'开始函数{funx.__name__}调用!')
    6. funx(var1,var2)
    7. print(f'结束函数{funx.__name__}调用!')
    8. return closure_test
    9. @log
    10. def functiontest(var1: str, var2: list[int]) -> str:
    11. """
    12. 函数测试
    13. :param var1: 初始字符串
    14. :param var2: 整型列表
    15. :return: 整合后的列表
    16. """
    17. for i in var2:
    18. var1 += str(i)
    19. print(f'最终结果为{var1}')
    20. return var1
    21. functiontest('1',(2,3,4,5))
    22. print(functiontest.__name__)
    23. print(functiontest.__doc__)
    24. print(functiontest.__annotations__)

    结果为返回被装饰函数属性:

    1. 开始函数functiontest调用!
    2. 最终结果为12345
    3. 结束函数functiontest调用!
    4. functiontest
    5. 函数测试
    6. :param var1: 初始字符串
    7. :param var2: 整型列表
    8. :return: 整合后的列表
    9. {'var1': <class 'str'>, 'var2': list[int], 'return': <class 'str'>}
  • 相关阅读:
    Java:泛型
    Nim 游戏
    Linux 查看密码修改记录
    leetcode Top100(24) // 环形链表2
    [Java] 异常的使用
    2022杭电多校6(总结+补题)
    kile5上的一栏快捷键消失了,我手贱删了
    2-11 基于matlab的BP-Adaboost的强分类器分类预测
    开源大模型ChatGLM2-6B 2. 跟着LangChain参考文档搭建LLM+知识库问答系统
    可复现的语言大模型推理性能指标
  • 原文地址:https://blog.csdn.net/davidksatan/article/details/125420898