三 高阶函数, 闭包和装饰器

所谓高阶函数就是把函数做为参数传入的一类函数. 另外, 返回函数的函数也属于高阶函数.

3.1 函数做为参数传入另外一个函数

3.1.1 高阶函数引入

Python中函数也是对象:

>>> type(abs)
<class 'builtin_function_or_method'>

因此也可以将函数赋值给其它变量, 当然该变量也可以像调用函数一样进行使用.

>>> f=abs
>>> type(f)
<class 'builtin_function_or_method'>
>>> f(-1)
1

既然函数可以赋值给一个变量, 那么当然也可以最为参数传入另外一个函数进行使用:

>>> def add(f, x, y):
...     return f(x)+f(y)
...
>>> add(abs, -1, -1)
2

3.1.2 Python内置的高阶函数

map()filter()是两个内置函数, 它们复制了生成器表达式的功能. 因此它们都返回的是一个迭代器.

3.1.2.1 map()

map(func, *iterables), 由函数声明的原型我们可以看出: map()函数可以接收一个函数和多个可迭代对象.

  • 当只传入一个可迭代对象时, map()的作用就是把函数func逐个施加于可迭代对象的每一个元素, 结果以迭代器返回:
>>> m = map(lambda x: x*x, range(10))
>>> type(m)
<class 'map'>
>>> from collections.abc import Iterable
>>> from collections.abc import Iterator
>>> isinstance(m, Iterable) # map是可迭代对象
True
>>> isinstance(m, Iterator) # map也是迭代器
True

既然map()返回的是 一个迭代器实例, 因此可以用在for循环中:

>>> for n in m:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81
  • map()也可以接受多个可迭代对象做为参数:
>>> m=map(lambda x, y: str(x)+str(y), ['x']*10, range(10))
>>> list(m)
['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9']

3.1.2.2 filter()

filter(predicate, iterable), 由函数声明原型可知: filter()是将断言应用到可迭代对象的每个元素, 将断言为真的元素以迭代器返回.

>>> def is_even(x): # 判断一个数为偶数
...     return x % 2 == 0
...
>>> list(filter(is_even, range(10)))
[0, 2, 4, 6, 8]

实际上, map()filter()完成的功能大部分都可以使用列表推断来完成.

3.1.3 其它常用的高阶函数

  • functools.reduce(func, iterable[, initializer]): 使用一个带有两个参数的函数来累积可迭代对象中的元素, 从左到右进行, 最后返回一个计算的值.
>>> reduce(lambda x,y: x*y, [1,2,3,4])
24
>>> reduce(lambda x,y: x+y, [1,2,3,4])
10

3.2 返回函数的函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

3.2.1 函数的嵌套

当一个函数定义中又放置了另外的一个函数定义, 那么就构成函数的嵌套定义:

g_var = "hello global variable!" # 全局变量(globa variable)
print(abs) # 内置变量(builtins variable)
def outer():print(g_var)e_var="hello enclosed variable!" # 局部变量(但也称为enclosed variable)def inner():print(e_var)l_val="hello local variable!" # 局部变量(local variable)print(l_val)inner()outer()

3.2.1.1 变量作用域

有了嵌套函数的定义, 那么就不得不说说Python中变量的作用域, Python中变量的作用域分为四种:

  • Local variable: 局部作用域, 也就是函数内部定义的变量都是局部变量, 也包含函数的参数.

  • Enclosed variable: 封闭作用域, 当在一个函数定义中内置了另外一个函数的定义, 那么外层函数中的局部变量就称为封闭作用域变量.

  • Global variable: 全局作用域, 在模块中定义的变量就是全局变量.

  • Builtins varable: 在Python builtins中定义的变量. 下面是查看builtins中的定义:

import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

3.2.1.2 解释器查找变量的原则

Python解释器查找变量的原则是:L -> E -> G -> B .

分析上述代码中内嵌函数的代码:

def inner():print(e_var)l_val="hello local variable!" # 局部变量(local variable)print(l_val)

解释器遇到print(e_val)时, 首先在局部作用域(L)中查找, 结果没有定义, 所以就到外层函数定义中查找, 也就是封闭作用域中(E), 因为外层函数中定义了e_val, 所以就可以输出hello enclosed variable!.

def outer():print(g_var)e_var="hello enclosed variable!" # 局部变量(但也称为enclosed variable)def inner():print(e_var)l_val="hello local variable!" # 局部变量(local variable)print(l_val)

从上面的示例我们可以得到如下结论:

Python对变量的查找总是始于所在作用域, 如果所在作用域没有定义就会往上一级作用域查找!

下面是一段有问题的代码:

g_var = "hello global variable!"
print(abs)
def outer():print(g_var)e_var="hello enclosed variable!"def inner():e_var = e_var + "+ local varialb"print(e_var)inner()outer()

上述代码运行会抛出异常:UnboundLocalError: local variable 'e_var' referenced before assignment

异常抛出的位置就在这行代码:e_var = e_var + "+ local varialb", 原因分析如下:

这就是一条赋值语句. 根据运算符优先级, 先执行等号右侧的字符串+运算, 那么在这里先访问变量e_var, 于是解释器开始在本作用域查找该变量的定义, 如果找不到才会去外曾查找, 但是一定要注意python定义变量就是通过赋值语句完成的, 所以这里其实是有变量的定义, 但是违背了先定义后使用的原则! 因此抛出了异常.

3.2.1.3 作用域提升

所谓变量作用域提升,就是指在嵌套函数中修改封闭作用域中的变量, 以及在封闭作用域中修改全局变量的手段.

  • nonlocal关键字用于嵌套函数内提升作用域:
g_var = "hello global variable!"
print(abs)
def outer():print(g_var)e_var="hello enclosed variable!"def inner():nonlocal e_vare_var = e_var + "+ local varialb"print(e_var)inner()outer()

上述代码通过nonlocal关键字告诉解释器, 这是在封闭作用域定义的那个e_var, 因此程序就可以正确运行了.

global关键字用于函数中提升局部变量为全局变量:

g_var = "hello global variable!"
print(abs)
def outer():print(g_var)e_var="hello enclosed variable!"def inner():global g_varg_var = g_var + "+ local varialb"print(g_var)inner()outer()

同样的道理, 这里的关键字global用于告诉解释器这里的g_var就是全局作用域定义的.

3.2.2 闭包

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。也就是说闭包必须是嵌套函数的定义, 而且内部函数引用了外部函数定义的变量.

def outer():i = 0def inner(name):nonlocal ii += 1print("{} 第 {} 次运行输出".format(name, i))return inner

上面代码中在outer函数中又定义了函数inner, 并且内部函数可以访问外部函数的参数或局部变量, 当outer返回函数inner时, 相关的参数和变量都保存在返回的函数中, 这种称为闭包的程序结构具有强大的能力.

添加测试代码如下:

...
if __name__ == '__main__':f1 = outer()f1('f1')f1('f1')f1('f1')f2 = outer()f2('f2')f1('f1')f2('f2')

运行输出:

f1 第 1 次运行输出
f1 第 2 次运行输出
f1 第 3 次运行输出
f2 第 1 次运行输出
f1 第 4 次运行输出
f2 第 2 次运行输出

从这个例子我们可以看出在闭包中, 内层嵌套函数引用的外层变量又如下特定:

外层函数的变量只与具体的闭包关联. 闭包的每个实例引用的外层函数的变量互不干扰.

一个闭包实例对外层变量的修改会被传递到下一次该闭包实例的调用.

3.3 装饰器

闭包又很多应用, 其中装饰器就是一个常用的场景.装饰器的作用通常用于丰富一个已经存在的函数的功能. 尤其是在拿不到原来函数定义的源代码时.

3.3.1 简单的装饰器

比如我们希望每次在调用一个函数时,添加一个日志记录, 通过装饰器来完成, 代码如下:

from datetime import datetime
def log(func):def wrapper(*args, **kwargs):print('call {}(): at {}'.format(func.__name__, datetime.now()))return func(*args, **kwargs)return wrapper

观察上面的log, 因为它接收一个函数做为参数, 并返回一个函数, 在内层函数中, 首先记录了函数调用的时间, 然后再调用该函数. 这就是一个装饰器. 装饰器的使用要通过@语法完成.

@log
def hello():print('hello')

@log放置在一个函数前面, 那么这个函数就是一个被装饰的函数了, 它不仅可以完成原有的功能, 而且还具有了装饰器为其添加的功能.

>>> hello()
call hello(): at 2021-05-03 20:44:01.184195
hello

另一方面, 把@log 放置在函数定义处, 相对于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是wrapper(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

3.3.2 带有参数的装饰器

如果装饰器本身需要传入参数, 那就需要编写一个返回装饰器的高阶函数, 写出来会稍微复杂一点. 比如, 带有自定义文本的log装饰器:

def log(text):def decorator(func):def wrapper(*args, **kwargs):print('{} call {}(): at {}'.format(text,func.__name__, datetime.now()))return func(*args, **kwargs)return wrapperreturn decorator

这个带有参数的装饰器使用如下, 首先通过@log(text)来装饰一个函数:

@log('execute')
def hello():print('hello')

然后执行该函数, 结果如下:

>>> hello()
execute call hello(): at 2021-05-04 07:54:17.025915
hello

和两层嵌套的装饰器相比, 三层嵌套的效果相当于执行下面的语句:

hello=log('execute')(hello)

也就是说, 首先执行log('execute'), 返回的是decorator函数, 再调用返回的函数, 参数是hello函数, 返回值最终是wrapper函数.

3.3.3 自定义装饰器的完善

尽管现在我们的装饰器已经能够加强一个函数的功能, 但是还有一个瑕疵, 那就是函数的__name__属性依然能暴露其本来的面目, 这就为依赖函数签名的一些应该留下了隐患.

>>> hello.__name__
'wrapper'

很明显, 现在函数hello的底层签名依然是wrapper, 而不是hello!

Python的标准模块functools可以帮助我们完成需要的功能, 一个完整的装饰器如下:

import functools
from datetime import datetimedef log(text):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print('{} call {}(): at {}'.format(text,func.__name__, datetime.now()))return func(*args, **kwargs)return wrapperreturn decorator@log('execute')
def hello():print('hello')

测试如下:

>>> hello()
execute call hello(): at 2021-05-04 08:13:22.007876
hello
>>> hello.__name__
'hello'

注意: 装饰器@functools.wraps(func)一定要放置在wrapper前面.

Python高阶函数原理分析及其应用相关推荐

  1. python四大高阶函数_详谈Python高阶函数与函数装饰器(推荐)

    一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首 ...

  2. 快速记忆python函数-【速学速记】Python 高阶函数

    原标题:[速学速记]Python 高阶函数 前言: python 迅速发展壮大起来,多亏了人工智能,AI ,机器学习,深度学习的发展.所以小的给各位看官们准备了收藏已久的视频教程分享给大家!希望能带给 ...

  3. python高阶函数filter_python 高阶函数之filter

    前文说到python高阶函数之map,相信大家对python中的高阶函数有所了解,此次继续分享python中的另一个高阶函数filter. 先看一下filter() 函数签名 >>> ...

  4. python高阶函数map_简单了解python高阶函数map/reduce

    高阶函数map/reduce Python内建了map()和reduce()函数. 我们先看map.map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每 ...

  5. python高阶函数(三分钟读懂)

    python高阶函数(三分钟读懂) 函数式编程 Python中,函数是一等对象 一等对象:具有特点 ① 对象是在运行时创建的 ② 能赋值给变量或作为数据结构中的元素 ③ 能作为参数传递 ④ 能作为返回 ...

  6. python高阶函数——sorted排序算法

    python高阶函数--sorted排序算法 python 内置的sorted()函数可以对一个list进行排序: >>> sorted([8,3,8,11,-2]) [-2, 3, ...

  7. python高阶函数闭包装饰器_Python自学从入门到就业之高阶函数、嵌套函数、闭包、装饰器...

    高阶函数 在Python中,函数其实也是一种数据类型. def test(): return 'hello world' print(type(test)) # 函数对应的数据类型是 function ...

  8. python高阶函数filter_Python进阶系列连载(13)——Python内置高阶函数filter(上)...

    前言 进阶部分连载继续~ 如果还没看过我的入门连载部分,先看: 当然,小编的免费入门课程已经有咯,看过连载的朋友可以看看视频再快速梳理一遍~ 前文传送门: filter filter是什么意思呢? 我 ...

  9. python高阶函数闭包装饰器_5.初识python装饰器 高阶函数+闭包+函数嵌套=装饰器...

    一.什么是装饰器? 实际上装饰器就是个函数,这个函数可以为其他函数提供附加的功能. 装饰器在给其他函数添加功能时,不会修改原函数的源代码,不会修改原函数的调用方式. 高阶函数+函数嵌套+闭包 = 装饰 ...

最新文章

  1. C#方法参数传递-同时使用ref和out关键字
  2. linux 在某个core上的中断 affinity c语言函数,Linux中断处理体系结构
  3. java mvc中重复提交表单,spring mvc 防止重复提交表单的两种方法,推荐第二种
  4. 信息学奥赛一本通 2052:【例3.2】范围判断
  5. 从事7年前端开发,有些经验想对转行学习前端的伙伴说说!
  6. matlab程序中for,matlab中的for循环
  7. 判断某点在多边形内——方法二
  8. 一些常用软件镜像地址
  9. 阿里云禁止root用户直接登录的解决办法
  10. 芯片程序保护-常规芯片加密方式
  11. 方舟:生存进化官服和私服区别
  12. 关于sql语句左连接(右连接)与条件同时存在的总结
  13. python的日志模块:logging;django的日志系统;django日志输出时间修改
  14. 菜鸟的MySQL学习之旅(二)—查询语句
  15. 拓嘉辰丰电商:拼多多一件代发三大优势有哪些
  16. 破解大学寝室智能限电
  17. postgresql 流复制切换
  18. 【英语考研词汇训练营】Day 17 —— espresso,ultimate,gradually,detect,dimension
  19. 计算机cad相关证书,cad证书含金量 工资一般是多少
  20. linux虚拟机管理面板,linux 虚拟主机面板(免费虚拟主机控制面板)

热门文章

  1. scanf()的用法总结
  2. win2003服务器管理功能介绍
  3. 墨画子卿第三章第3节:回归祖庙
  4. 【JavaScript 教程】事件——EventTarget 接口
  5. 详细解释sprintf 函数(转)
  6. 你的交际力能否通吃?
  7. vscode 程序员鼓励师_把软萌程序猿鼓励师装进 VScode 里?GitHub 2.5k 星标,爱上写代码...
  8. 酒店行业如何建立积分体系促进会员消费?
  9. 10__jsp入门el表达式入门CookieSession
  10. QT 的 QSS 的基本概念