唐抉的个人博客

python学习笔记(五)

字数统计: 2.6k阅读时长: 11 min
2022/10/10

高阶函数

变量可以指向函数,函数的参数也能接受变量。当一个函数可以接收另一个函数作为参数时,这个函数被称作高阶函数。

最简单的高阶函数如下:

1
2
3
4
5
def add(x,y,f):
return f(x)+f(y)
print(add(-5,6,abs))
#运行结果如下:
11

map/reduce函数

map()函数

map()函数接收两个参数,一个是函数,另一个是Iterablemap将传入的函数依次作用到序列的每个元素,并将结果作为新的Iterable返回。

例如要将函数f(x)=x^2作用到一个list[1,2,3,4,5,6,7,8,9]上:

1
2
3
4
5
6
def f(x):
return x*x
r=map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))
#运行结果如下:
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce()函数

reduce()把函数作用到一个序列[x1,x2,x3,…]上,reduce接收两个函数后,把结果继续和序列的下一个元素做累计计算。

例如要对一个序列求和:

1
2
3
4
5
6
from functools import reduce
def add(x,y):
return x+y
print(reduce(add,[1,3,5,7,9]))
#运行结果如下:
25

map()reduce()函数结合,可以写出把str转换为int的函数:

1
2
3
4
5
6
7
8
9
from functools import reduce
def fn(x,y):
return x*10+y
def char2num(s):
d={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
return d[s]
print(reduce(fn,map(char2num,'14512')))
#运行结果如下:
14512

练习题

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']

代码如下:

1
2
3
4
5
6
7
8
9
def normalize(name):
for i in name:
return str.title(name)
# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
#运行结果如下:
['Adam', 'Lisa', 'Bart']

Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

1
2
3
4
5
6
7
8
9
10
11
12
13
from functools import reduce
def prod(L):
return reduce(fn,L)
def fn(x,y):
return x*y
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
print('测试成功!')
else:
print('测试失败!')
#运行结果如下:
3 * 5 * 7 * 9 = 945
测试成功!

利用mapreduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#方法一:
from functools import reduce
def str2float(s):
def fn(x,y):
return x*10+y
def gn(x,y):
return x*0.1+y
def char2num(t):
d={str(x):x for x in range(10)}
return d[t]
a=s.index('.')
t1=s[:a]
t2=s[a+1:]
t2=t2[::-1]
p=reduce(fn,map(char2num,t1))
q=reduce(gn,map(char2num,t2))*0.1
return p+q

print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')

#方法二:
from functools import reduce
def str2float(s):
d={str(x):x for x in range(10)}
p=reduce(lambda x,y:x*10+y,map(lambda s:d[s],s[:s.find('.')]))
q=reduce(lambda x,y:x*10+y,map(lambda s:d[s],s[s.find('.')+1:]))*0.001
return p+q

print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')
#运行结果如下:
str2float('123.456') = 123.456
测试成功!

filter函数

filter()函数用于过滤序列,该函数接收一个函数和一个序列。与map()不同的是,filter()把传入的函数依次作用于每个元素后,根据返回值是True还是False来决定是保留还是丢弃该元素。

例如在一个list中删掉偶数,只保留奇数:

1
2
3
4
5
def odd(n):
return n%2==1
print(list(filter(odd,[1,2,4,5,6,9,10,15])))
#运行结果如下:
[1, 5, 9, 15]

filter()函数返回 的是一个Iterator,因此要强迫filter()完成计算结果就需要用list()函数来获得所以结果并返回list。

用filter求1000以内素数例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _odd_iter(): #先构造从3开始的奇数列
n = 1
while True:
n = n + 2
yield n
def _not_divisible(n): #筛选函数
return lambda x: x % n > 0
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
# 打印1000以内的素数:
for n in primes():
if n < 1000:
print(n)
else:
break

练习题

回数是指从左向右读和从右向左读都是一样的数,例如12321909。请利用filter()筛选出回数:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def is_palindrome(n):
return str(n)==str(n)[::-1]
# 测试:
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [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]:
print('测试成功!')
else:
print('测试失败!')
#运行结果如下:
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]
测试成功!

sorted函数

sorted()可以对list进行排序,还可以接收一个key函数来实现自定义排序。

比如按绝对值大小排序:

1
2
3
print(sorted([36,5,-12,9,-21],key=abs))
#运行结果如下:
[5, 9, -12, -21, 36]

默认情况下,用sorted()对字符串排序是按照ASCII的大小比较的,若要忽略大小写按照字母表排序,则可以写成:

1
2
3
4
5
6
7
#正序排序
print(sorted(['Bob','ant','Zoo','panda'],key=str.lower))
#反序排序
print(sorted(['Bob','ant','Zoo','panda'],key=str.lower,reverse=True))
#运行结果如下:
['ant', 'Bob', 'panda', 'Zoo']
['Zoo', 'panda', 'Bob', 'ant']

练习题

假设我们用一组tuple表示学生名字和成绩:

1
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

请用sorted()对上述列表分别按名字排序:

代码如下:

1
2
3
4
5
6
7
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return t[0].lower()
L2 = sorted(L, key=by_name)
print(L2)
#运行结果如下:
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

再按成绩从高到低排序:

1
2
3
4
5
6
7
8
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_score(t):
reverse=True
return -t[1]
L2 = sorted(L, key=by_score)
print(L2)
#运行结果如下:
[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]

返回函数

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

例如,实现一个可变参数的求和,根据需要计算结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
def lazy_sum(*args):
def sum():
a=0
for n in args:
a=a+n
return a
return sum
f=lazy_sum(1,2,3,4,5)
print(f)
print(f())
#运行结果如下:
<function lazy_sum.<locals>.sum at 0x7f3aaacb9ca0>
15

在调用lazy_sum()函数时,返回的是求和函数而不是求和结果。调用函数f时才真正计算求和的结果。当调用lazy_sum()函数时,每次调用都会返回一个新的函数,即使传入相同参数,返回的函数也不会相同。

闭包

上面的例子中,内部函数sum可以引用外部函数lazy_sum()的参数和局部变量。当lazy_sum()返回函数sum时,相关参数和变量仍保存在返回的函数中。这种程序结构便称为闭包。

例如,在下面例子中f1(),f2()f3()调用的结果应该是1,4,9,但实际结果是9 9 9。这便是由于返回函数引用了变量i,但它并非立刻执行,等到3个函数都返回时,所引用的变量已经变成了3,故最终结果为9

1
2
3
4
5
6
7
8
9
10
11
def count():
fn=[]
for i in range(1,4):
def f():
return i*i
fn.append(f)
return fn
f1,f2,f3=count()
print(f1(),f2(),f3())
#运行结果如下:
9 9 9

注意:返回函数不要引用任何循坏变量或后续会发生变化的变量。若一定要引用循环变量,便再创建一个函数,用该函数的参数绑定循环变量当前的值,具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
def count():
def g(i):
def f():
return i*i
return f
fn=[]
for i in range(1,4):
fn.append(g(i))
return fn
f1,f2,f3=count()
print(f1(),f2(),f3())
#运行结果如下:
1 4 9

nonlocal

使用闭包,就是内层函数引用了外层函数的局部变量。如果只读外层变量的值,返回的闭包函数调用一切正常,但对外层函数的局部变量进行运算就会报错。

若内层函数想要计算外层函数的局部变量,需要在内部函数内部加一个nonlocal 局部变量名的声明。因此在使用闭包时,对外层变量赋值前,需要使用nonlocal声明改变了不是当前函数的局部变量。

练习题

利用闭包返回一个计数器函数,每次调用它返回递增整数:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def createCounter():
x=0
def counter():
nonlocal x
x=x+1
return x
return counter
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
#运行结果如下:
1 2 3 4 5
测试通过!

匿名函数

传入函数时,有时不需要显示定义函数,直接传入匿名函数更方便。如匿名函数lambda x:x*x实际上就是:

1
2
def f(x):
return x*x

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。由于匿名函数没有名字,不必担心函数名冲突。匿名函数是一个函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数,也可以把匿名函数作为返回值返回。

练习题

请用匿名函数改造下面的代码:

1
2
3
4
5
def is_odd(n):
return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))
print(L)

代码如下:

1
2
3
print(list(filter(lambda n:n % 2 == 1, range(1, 20))))
#运行结果如下:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
CATALOG
  1. 1. 高阶函数
    1. 1.1. map/reduce函数
      1. 1.1.1. map()函数
      2. 1.1.2. reduce()函数
      3. 1.1.3. 练习题
    2. 1.2. filter函数
      1. 1.2.1. 练习题
    3. 1.3. sorted函数
      1. 1.3.1. 练习题
  2. 2. 返回函数
    1. 2.1. 闭包
    2. 2.2. nonlocal
    3. 2.3. 练习题
  3. 3. 匿名函数
    1. 3.1. 练习题