Python高阶函数原理分析及其应用
Python高阶函数原理分析及其应用
三 高阶函数, 闭包和装饰器
所谓高阶函数就是把函数做为参数传入的一类函数. 另外, 返回函数的函数也属于高阶函数.
3.1 函数做为参数传入另外一个函数
3.1.1 高阶函数引入
Python中函数也是对象:
>>> type(abs)
<class ‘builtin_function_or_method’>
因此也可以将函数赋值给其它变量, 当然该变量也可以像调用函数一样进行使用.
>>> f=abs
>>> type(f)
<class ‘builtin_function_or_method’>
>>> f(-1)
既然函数可以赋值给一个变量, 那么当然也可以*为参数传入另外一个函数进行使用:
>>> def add(f, x, y):
… return f(x)+f(y)
…
>>> add(abs, -1, -1)
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’]
1
2
3
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_var
e_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_var
g_var = g_var + “+ local varialb”
print(g_var)
inner()
outer()
同样的道理, 这里的关键字global用于告诉解释器这里的g_var就是全局作用域定义的.
3.2.2 闭包
简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。也就是说闭包必须是嵌套函数的定义, 而且内部函数引用了外部函数定义的变量.
def outer():
i = 0
def inner(name):
nonlocal i
i += 1
print(“{} 第 {} 次运行输出”.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 wrapper
return 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 datetime
def 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 wrapper
return decorator
@log(‘execute’)
def hello():
print(‘hello’)
测试如下:
>>> hello()
execute call hello(): at 2021-05-04 08:13:22.007876
hello
>>> hello.__name__
‘hello’