lambda、map 和 filter
在 Python 里,函数也是一个值,和数字、列表一样。可以给它起第二个名字,可以存起来,也可以交给另一个函数。这一课从三个地方用上这一点:短小的 lambda 函数、按 key 排序,还有内置函数 map 和 filter。
函数也是值
函数名不加括号,就是函数本身;加了括号,才是调用它。
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 自己不知道要对每个东西做什么,由调用它的人决定:
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("像素小镇"))
f = len 给内置函数 len 多起了一个名字。调用 f("像素小镇") 就是调用 len("像素小镇")。
lambda
lambda 是用一个表达式写成的小函数。它没有自己的名字,也不写 return:表达式的值就是它的返回值。
1double = lambda n: n * 22add = lambda a, b: a + b3print(double(5))4print(add(3, 4))
10 7
def | lambda | |
|---|---|---|
| 名字 | def double(n): | 没有;可以存进一个变量 |
| 参数 | 写在括号里 | 写在 lambda 和冒号之间 |
| 函数体 | 要几行写几行 | 只能是一个表达式 |
| 结果 | return ... | 表达式的值,自动返回 |
像上面那样把 lambda 存进变量也行,但一个函数值得起名字的话,用 def 更清楚。lambda 最适合直接写在要用它的地方,当另一个函数的实参。这一课后面都是这样用的。
按 key 排序
sorted() 和列表的 .sort() 方法都能给一个 key:一个函数,对每个东西调用一次。排序时比的是这个函数返回的值,而不是东西本身。
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:
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))
key=abs 按离 0 有多远来排:1、3、5、8。key 只用来比较,列表里放的还是原来的数,负号也还在。
map
map(函数, 一串东西) 对每个东西调用一次这个函数。它返回一个 map 对象,结果一个一个地往外给;套上 list() 就能一下子拿到全部。
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() 把结果全拿走以后,就什么都不剩了:
1results = map(str.upper, ["iron", "gold"])2print(list(results))3print(list(results))
['IRON', 'GOLD'] []
filter
filter(函数, 一串东西) 留下函数返回 True(或者任何真值)的东西,其他的扔掉。和 map 一样,它返回一个对象,要套上 list()。
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] |
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 最合适的地方。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
便宜的在前
✓把商店里的东西按价格排好再显示,便宜的在前。
[('绳子', 3), ('灯', 12), ('镐', 25)]1items = [("灯", 12), ("绳子", 3), ("镐", 25)]2print(sorted(items))
一行数加起来
✓用 map 把 line 拆出来的每一段变成数字,再显示它们的和。
50
1line = "4 15 8 23"2parts = line.split()3print(parts)
没油的灯
✓用 filter 和 lambda,显示一个列表,里面是油已经用完(0)的灯。(练习用;推导式也行。)
['磨坊', '广场']
1oil = {"北门": 5, "磨坊": 0, "桥头": 2, "广场": 0}2print(list(oil))
这一课学会了
- 函数也是值:函数名不加括号是函数本身,加了括号是调用。
lambda 参数: 表达式写出一个没有名字的小函数,返回那个表达式的值。sorted、.sort、max、min都能给key=,用一个函数说明比什么。map(f, 一串东西)对每个都调用f;filter(f, 一串东西)留下f为真的。要套上list(),而且只能用一遍。- 要在
map或filter里写 lambda 的,通常推导式更清楚。