高阶函数(Higher-order function)
Input:
1 |
abs |
Output:
1 |
<function abs> |
Input:
1 |
abs(-10) |
Output:
1 |
10 |
abs
是函数本书,abs(-10)
是函数调用
Input:
1 |
f = abs |
Output:
1 |
<function abs> |
变量可以指向函数
Input:
1 |
f(-10) |
Output:
1 |
10 |
函数名也是变量
函数名其实就是指向函数的变量,对于abs()
这个函数,完全可以把abs
看成变量,它指向一个可以计算绝对值的函数,如果把abs
指向其他对象,将无法再调用计算绝对值的函数
Input:
1 |
abs = 10 |
Output:
1 |
--------------------------------------------------------------------------- |
abs函数实际上是定义在
import builtins
模块中的
传入函数
一个函数接收另外一个函数作为参数,这种函数就称之为高阶函数
Input:
1 |
def (x, y, f): |
1 |
print(add(-5, 6, abs)) |
Output:
1 |
11 |
map/reduce
基本知识
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
Input:
1 |
def f(x): |
Output:
1 |
[1, 4, 9, 16, 25, 36, 49, 64, 81] |
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(f(x1, x2), x3), x4)
对一个序列求和,就可以用reduce
实现
Input:
1 |
from functools import reduce |
Output:
1 |
25 |
我们配合map()
,可以写出把str
转换为int
的函数:
Input:
1 |
from functools import reduce |
Output:
1 |
13579 |
整理成一个str2int
的函数就是
1 |
from functools import reduce DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def str2int(s): |
还可以用lambda
函数进一步简化
1 |
from functools import reduce DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def char2num(s): |
练习
利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:
Input:
1 |
def normalize(name): |
1 |
res = normalize(['adam', 'LISA', 'barT']) |
Output:
1 |
['Adam', 'Lisa', 'Bart'] |
Python提供的sum()
函数可以接受一个list并求和,请编写一个prod()
函数,可以接受一个list并利用reduce()
求积:
Input:
1 |
from functools import reduce |
1 |
prod([3,5,7,9]) |
Output:
1 |
945 |
利用map
和reduce
编写一个str2float
函数,把字符串123.456
转换成浮点数123.456
Input:
1 |
from functools import reduce |
1 |
print('str2float('123.456') =', str2float('123.456')) |
Output:
1 |
str2float('123.456') = 123.456 |
filter
基本知识
filter()
函数用于过滤序列。和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写
Input:
1 |
def is_odd(n): |
Output:
1 |
[1, 5, 9, 15] |
把一个序列中的空字符串删掉
Input:
1 |
def not_empty(s): |
Output:
1 |
['A', 'B', 'C'] |
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list
用filter求素数
用python实现使用埃氏筛法计算素数
可以先构造一个从3
开始的奇数序列,这是一个生成器,并且是一个无限序列
1 |
def _odd_iter(): |
然后定义一个筛选函数
1 |
def _not_divisible(n): |
最后,定义一个生成器,不断返回下一个素数
1 |
def primes(): |
1 |
# 打印30以内的素数: |
Output:
1 |
2 |
注意到Iterator
是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。
练习
回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
Input:
1 |
def is_palindrome(n): |
1 |
output = filter(is_palindrome, range(1, 1000)) |
Output:
1 |
1~1000: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999] |
filter()
的作用是从一个序列中筛出符合条件的元素。由于filter()
使用了惰性计算,所以只有在取filter()
结果的时候,才会真正筛选并每次返回下一个筛出的元素。
sorted
基本知识
Python内置的sorted()函数可以对list进行排序
Input:
1 |
sorted([36, 5, -12, 9, -21]) |
Output:
1 |
[-21, -12, 5, 9, 36] |
sorted()
函数也是一个高阶函数,可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序
Input:
1 |
sorted([36, 5, -12, 9, -21], key=abs) |
Output:
1 |
[5, 9, -12, -21, 36] |
对字符串进行排序
Input:
1 |
sorted(['bob', 'about', 'Zoo', 'Credit']) |
Output:
1 |
['Credit', 'Zoo', 'about', 'bob'] |
实现忽略大小写的排序
Input:
1 |
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) |
Output:
1 |
['about', 'bob', 'Credit', 'Zoo'] |
进行反向排序
Input:
1 |
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) |
Output:
1 |
['Zoo', 'Credit', 'bob', 'about'] |
练习
假设我们用一组tuple表示学生名字和成绩:
L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
请用sorted()
对上述列表分别按名字排序:
Input:
1 |
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] |
1 |
by_name(L) |
Output:
1 |
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)] |
再按成绩从高到低排序
Input:
1 |
def by_score(t): |
1 |
by_score(L) |
Output:
1 |
[('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)] |
返回函数
基本知识
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
定义一个求和函数,不需要立刻得到求和结果,而是在后面的代码中根据需要再计算。
1 |
def lazy_sum(*args): |
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数
Input:
1 |
f = lazy_sum(1,3,5,7,9) |
1 |
f |
Output:
1 |
<function __main__.lazy_sum.<locals>.sum> |
当调用函数f
时,才真正计算求和的结果
Input:
1 |
f() |
Output:
1 |
25 |
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
闭包
注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。
Input:
1 |
def count(): |
Input:
1 |
f1() |
Output:
1 |
9 |
Input:
1 |
f2() |
Output:
1 |
9 |
Input:
1 |
f3() |
Output:
1 |
9 |
我们可能会认为调用f1()
、f2()
和f3()
结果应该是1
,4
,9
,但实际结果全是9
。原因就在于返回的函数引用了变量i
,但它非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3
,因此最终结果为9
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环表按量后续如何更改,已绑定到函数参数的值不变:
Input:
1 |
def count(): |
1 |
f1, f2, f3 = count() |
Input:
1 |
f1() |
Output:
1 |
1 |
Input:
1 |
f2() |
Output:
1 |
4 |
Input:
1 |
f3() |
Output:
1 |
9 |
练习
利用闭包返回一个计数器函数,每次调用它返回递增整数:
Input:
1 |
def createCounter(): |
1 |
# 测试: |
Output:
1 |
1 2 3 4 5 |
匿名函数
基本知识
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。关键字lambda
表示匿名函数,冒号前面的x
表示函数参数
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
Input:
1 |
f = lambda x: x*x |
Output:
1 |
25 |
练习
请用匿名函数改造下面的代码:
Input:
1 |
def is_odd(n): |
1 |
L |
Output:
1 |
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] |
Input:
1 |
L = list(filter(lambda x: x%2 == 1, range(1, 20))) |
1 |
L |
Output:
1 |
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] |
装饰器
假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下
1 |
def log(func): |
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处
1 |
def now(): |
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志
Input:
1 |
now() |
Output:
1 |
call now(): |
把@log
放到now()
函数的定义处,相当于执行了语句now=log(now)
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本
1 |
def log(text): |
1 |
@log('execute') |
Input:
1 |
now() |
Output:
1 |
execute now(): |
和两层嵌套的decorator相比,三层嵌套的效果是now = log('execute')(now)
偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
创建一个把二进制的字符串转换为整数的偏函数
Input:
1 |
import functools |
Output: