Kodewick
第 19 课,共 31 课目录

lambda、map 和 filter

第 19 课,共 31 课 · 第 3 部分 · 函数 · 约 20 分钟

在 Python 里,函数也是一个值,和数字、列表一样。可以给它起第二个名字,可以存起来,也可以交给另一个函数。这一课从三个地方用上这一点:短小的 lambda 函数、按 key 排序,还有内置函数 map 和 filter。

函数也是值

函数名不加括号,就是函数本身;加了括号,才是调用它。

main.py
1def double(n):2    return n * 23 4twice = double5print(twice(5))6print(double(5))
输出
10
10

twice = double 什么都没运行,也没有复制一份,只是给同一个函数多起了一个名字。所以 twice(5) 和 double(5) 完全一样。

函数既然是值,就能当实参传给另一个函数。下面的 apply_to_all 自己不知道要对每个东西做什么,由调用它的人决定:

main.py
1def double(n):2    return n * 23 4def exclaim(text):5    return text + "!"6 7def apply_to_all(func, items):8    results = []9    for item in items:10        results.append(func(item))11    return results12 13print(apply_to_all(double, [1, 2, 3]))14print(apply_to_all(exclaim, ["灯", "矿石"]))
输出
[2, 4, 6]
['灯!', '矿石!']
先猜一猜

这段会显示什么?

1f = len2print(f("像素小镇"))

lambda

lambda 是用一个表达式写成的小函数。它没有自己的名字,也不写 return:表达式的值就是它的返回值。

main.py
1double = lambda n: n * 22add = lambda a, b: a + b3print(double(5))4print(add(3, 4))
输出
10
7
deflambda
名字def double(n):没有;可以存进一个变量
参数写在括号里写在 lambda 和冒号之间
函数体要几行写几行只能是一个表达式
结果return ...表达式的值,自动返回

像上面那样把 lambda 存进变量也行,但一个函数值得起名字的话,用 def 更清楚。lambda 最适合直接写在要用它的地方,当另一个函数的实参。这一课后面都是这样用的。

按 key 排序

sorted() 和列表的 .sort() 方法都能给一个 key:一个函数,对每个东西调用一次。排序时比的是这个函数返回的值,而不是东西本身。

main.py
1words = ["warehouse", "ore", "lamp", "mine"]2print(sorted(words))3print(sorted(words, key=len))4print(sorted(words, key=len, reverse=True))
输出
['lamp', 'mine', 'ore', 'warehouse']
['ore', 'lamp', 'mine', 'warehouse']
['warehouse', 'lamp', 'mine', 'ore']

key=len 时比的是单词的长度。lamp 和 mine 一样长,所以保持它们在原列表里的先后。

现成的函数不够用时,就用 lambda 写 key。下面每个矿工是一个元组,按第二项排序。max() 和 min() 也能给 key:

main.py
1miners = [("阿达", 12), ("小本", 30), ("克莱奥", 21)]2by_ore = sorted(miners, key=lambda m: m[1])3print(by_ore)4print(max(miners, key=lambda m: m[1]))5print(min(miners, key=lambda m: m[1]))
输出
[('阿达', 12), ('克莱奥', 21), ('小本', 30)]
('小本', 30)
('阿达', 12)
先猜一猜

这段会显示什么?

1numbers = [5, -8, 3, -1]2print(sorted(numbers, key=abs))

map

map(函数, 一串东西) 对每个东西调用一次这个函数。它返回一个 map 对象,结果一个一个地往外给;套上 list() 就能一下子拿到全部。

main.py
1text = "3 8 2 6"2loads = list(map(int, text.split()))3print(loads)4print(sum(loads))5print(list(map(lambda n: n * 10, loads)))
输出
[3, 8, 2, 6]
19
[30, 80, 20, 60]

第一个 map 是很常见的写法:split() 得到的是字符串,map(int, ...) 把每一个都变成数字。直接打印 map 对象本身,只会看到 <map object at 0x...> 这样的东西。

map 对象只能走一遍。list() 把结果全拿走以后,就什么都不剩了:

main.py
1results = map(str.upper, ["iron", "gold"])2print(list(results))3print(list(results))
输出
['IRON', 'GOLD']
[]

filter

filter(函数, 一串东西) 留下函数返回 True(或者任何真值)的东西,其他的扔掉。和 map 一样,它返回一个对象,要套上 list()。

main.py
1loads = [3, 8, 2, 6, 11]2print(list(filter(lambda n: n >= 5, loads)))3codes = ["12", "ab", "7", "3x"]4print(list(filter(str.isdigit, codes)))
输出
[8, 6, 11]
['12', '7']

注意传的是 str.isdigit,不是 str.isdigit():filter 要的是方法本身,好拿去对每个编号调用。

推导式往往更清楚

map 和 filter 能做的,推导式都能做:

要做什么map / filter推导式
每个都变一变list(map(lambda n: n * 2, loads))[n * 2 for n in loads]
只留下一部分list(filter(lambda n: n > 5, loads))[n for n in loads if n > 5]
两样都要list(map(lambda n: n * 2, filter(lambda n: n > 5, loads)))[n * 2 for n in loads if n > 5]
main.py
1loads = [3, 8, 2, 6, 11]2with_map = list(map(lambda n: n * 2, filter(lambda n: n > 5, loads)))3with_comp = [n * 2 for n in loads if n > 5]4print(with_map)5print(with_comp)
输出
[16, 12, 22]
[16, 12, 22]

结果一样,但推导式从左往右读,就是一句大白话。一个好用的规矩:

  • 要在 map 或 filter 里写 lambda 的,改写成推导式。
  • 函数已经有名字的,比如 int、str.upper 或者你自己写的函数,map(int, parts) 又短又清楚,用它没问题。
  • sorted、max、min 里的 key=lambda ...,才是 lambda 最合适的地方。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

便宜的在前

把商店里的东西按价格排好再显示,便宜的在前。

要显示
[('绳子', 3), ('灯', 12), ('镐', 25)]
exercise1.py
1items = [("灯", 12), ("绳子", 3), ("镐", 25)]2print(sorted(items))
输出
点「运行」或按 Ctrl+Enter
练习 2

一行数加起来

用 map 把 line 拆出来的每一段变成数字,再显示它们的和。

要显示
50
exercise2.py
1line = "4 15 8 23"2parts = line.split()3print(parts)
输出
点「运行」或按 Ctrl+Enter
练习 3

没油的灯

用 filter 和 lambda,显示一个列表,里面是油已经用完(0)的灯。(练习用;推导式也行。)

要显示
['磨坊', '广场']
exercise3.py
1oil = {"北门": 5, "磨坊": 0, "桥头": 2, "广场": 0}2print(list(oil))
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 函数也是值:函数名不加括号是函数本身,加了括号是调用。
  • lambda 参数: 表达式 写出一个没有名字的小函数,返回那个表达式的值。
  • sorted、.sort、max、min 都能给 key=,用一个函数说明比什么。
  • map(f, 一串东西) 对每个都调用 f;filter(f, 一串东西) 留下 f 为真的。要套上 list(),而且只能用一遍。
  • 要在 map 或 filter 里写 lambda 的,通常推导式更清楚。