Python 的 operator 模块有什么用?itemgetter、attrgetter 怎么用?
简化版
**operator 模块把 Python 的「运算符」和「取值操作」封装成了「函数」——这样运算符也能像函数一样被传递、当作 key/回调使用,比写 lambda 更简洁、更快、更明确。**最常用的三个「取值工厂」:① itemgetter(k)——返回一个函数,取 obj[k](按下标/键取值),itemgetter(1) ≈ lambda x: x[1],还能一次取多个 itemgetter(0, 2) 返回元组;② attrgetter('name')——取 obj.name(按属性取),支持嵌套 attrgetter('a.b');③ methodcaller('m', args)——调用 obj.m(args)(调方法),methodcaller('upper') ≈ lambda x: x.upper()。最典型用途是排序/分组的 key:sorted(people, key=attrgetter('age'))、sorted(data, key=itemgetter(1)) 比 lambda 更快更清晰。此外:operator.add/mul/lt 等把 +/*/< 变成函数(reduce(operator.mul, nums)、operator.itemgetter 配合 sorted/max/groupby)。核心记忆:operator 把运算符和取值变函数;itemgetter 取下标/键、attrgetter 取属性、methodcaller 调方法,做 sorted/max/groupby 的 key 比 lambda 快且清晰。
详细版
operator 模块的常用工具:
| 工具 | 作用 | 等价 lambda |
|---|---|---|
itemgetter(1) | 取 x[1] | lambda x: x[1] |
itemgetter(0, 2) | 取多个 → 元组 | lambda x: (x[0], x[2]) |
attrgetter('age') | 取 x.age | lambda x: x.age |
attrgetter('a.b') | 嵌套属性 | lambda x: x.a.b |
methodcaller('upper') | 调 x.upper() | lambda x: x.upper() |
operator.add | a + b | lambda a,b: a+b |
operator.lt | a < b | lambda a,b: a<b |
from operator import itemgetter, attrgetter, methodcaller
import operator
# itemgetter:按下标/键取值(排序最常用)
data = [("Tom", 25), ("Amy", 30), ("Bob", 20)]
sorted(data, key=itemgetter(1)) # 按年龄 → [('Bob',20),('Tom',25),('Amy',30)]
sorted(data, key=itemgetter(0)) # 按名字
# 取多个(返回元组,多级排序)
rows = [{"name": "a", "age": 25, "score": 90}, ...]
sorted(rows, key=itemgetter("age", "score")) # 先按 age 再按 score
# attrgetter:按属性取值
class Person:
def __init__(self, name, age): self.name, self.age = name, age
people = [Person("Tom", 25), Person("Amy", 30)]
sorted(people, key=attrgetter("age")) # 按 age 排序
attrgetter("name.upper") # 嵌套:x.name.upper(属性链)
# methodcaller:调用方法
words = ["Hello", "WORLD", "PyThon"]
sorted(words, key=methodcaller("lower")) # 按小写排序
list(map(methodcaller("strip"), lines)) # 每行 strip
# operator 里的运算符函数
from functools import reduce
reduce(operator.mul, [1,2,3,4]) # 连乘 = 24(比 lambda a,b:a*b 清晰)
reduce(operator.add, [1,2,3]) # 求和(虽然用 sum 更好)
# 配合 groupby(必须先排序)
from itertools import groupby
data = sorted(data, key=itemgetter(1))
for key, group in groupby(data, key=itemgetter(1)):
print(key, list(group))
⚠️ 核心价值:
operator模块让你「把运算符/取值操作当函数传递」——排序、分组、归约这些需要传「key 函数」或「二元函数」的地方,用itemgetter/attrgetter/methodcaller代替 lambda,既更快(C 实现、无 Python 函数调用开销)又更明确(一眼看出是「取第 1 项」还是「取 age 属性」)。三个 getter 的分工很清晰:下标/键用itemgetter(x[k])、属性用attrgetter(x.attr)、方法用methodcaller(x.m())。它们在sorted、max、min、groupby、map的 key/函数参数里最常见。itemgetter和attrgetter还支持「一次取多个」——itemgetter(0, 2)返回(x[0], x[2])元组,正好用于「多级排序」(sorted(data, key=itemgetter("dept", "salary"))先按部门再按薪资)。至于operator.add/mul/lt这些「运算符函数」,主要在需要「把运算符当函数传」的场景用(reduce(operator.mul, ...)、sorted(key=...)里的比较),但求和、极值这些有专门内置(sum、max)时优先用内置。
完整版教学
一、operator 模块是什么
先理解 operator 的定位:
operator 模块:把"运算符和取值操作"封装成"函数"
Python 的 +、[]、.、方法调用 → 对应的函数
为什么需要:
很多场景要传"函数"(key、回调、reduce 的二元函数)
但运算符/取值不是函数,不能直接传:
sorted(data, key=???) # 想按 x[1] 排序
reduce(???, nums) # 想用 * 连乘
→ 要么写 lambda,要么用 operator 提供的现成函数
两类内容:
① 取值工厂(最常用):
itemgetter(k) → 函数:取 x[k]
attrgetter(a) → 函数:取 x.a
methodcaller(m) → 函数:调 x.m()
② 运算符函数:
operator.add(a,b) = a+b
operator.mul、sub、truediv、mod、pow
operator.lt/le/gt/ge/eq/ne(比较)
operator.and_/or_/not_(逻辑/位)
operator.getitem/setitem/contains
vs lambda:
itemgetter(1) 代替 lambda x: x[1]
→ 更快(C 实现)、更明确、可读
所以 operator 把运算符/取值封装成函数,用于要传函数的场景(key/reduce),比 lambda 快
operator 模块:把「运算符和取值操作」封装成「函数」(Python 的 +、[]、.、方法调用 → 对应的函数)。为什么需要:很多场景要传「函数」(key、回调、reduce 的二元函数)、但运算符/取值不是函数不能直接传(sorted(data, key=???) 想按 x[1] 排序),要么写 lambda 要么用 operator 的现成函数。两类内容:① 取值工厂(最常用):itemgetter(k)(取 x[k])、attrgetter(a)(取 x.a)、methodcaller(m)(调 x.m());② 运算符函数:operator.add/mul/sub、lt/le/gt/eq(比较)、and_/or_、getitem/setitem/contains。vs lambda:itemgetter(1) 代替 lambda x: x[1](更快、更明确、可读)。理解「operator 把运算符/取值封装成函数;用于要传函数的场景(key/reduce);两类:取值工厂(itemgetter/attrgetter/methodcaller)+运算符函数(add/mul/lt);比 lambda 快明确」,就理解了 operator 是什么。
二、itemgetter:按下标/键取值
理解 itemgetter:
itemgetter(k):返回一个函数,取 obj[k]
f = itemgetter(1)
f(["a","b","c"]) → "b"(取下标 1)
f({"age": 25, ...}) 不行(itemgetter(键) 才对字典)
itemgetter("age")({"age":25}) → 25(按键取)
取多个(返回元组):
itemgetter(0, 2)(["a","b","c","d"]) → ("a", "c")
→ 一次取多个位置,组成元组
用于多级排序:
sorted(data, key=itemgetter("dept", "salary"))
# 先按 dept 排,dept 相同再按 salary
最典型:sorted/max/min 的 key
# 按元组第 2 项排序
sorted([(1,'c'),(2,'a'),(3,'b')], key=itemgetter(1))
→ [(2,'a'),(3,'b'),(1,'c')]
# 找 score 最高的记录
max(records, key=itemgetter("score"))
vs lambda:
key=itemgetter(1) # 更快、更明确
key=lambda x: x[1] # 慢一点、要读 lambda
支持任何 __getitem__ 的对象:
列表、元组(下标)、字典(键)、字符串(下标)
itemgetter(0)("abc") → "a"
所以 itemgetter(k)取 obj[k](下标或键),取多个返回元组(多级排序),排序 key 最常用
itemgetter(k):返回一个函数、取 obj[k]——f = itemgetter(1)、f(["a","b","c"]) → “b”(取下标 1)、itemgetter("age")({"age":25}) → 25(按键取)。取多个(返回元组):itemgetter(0, 2)(["a","b","c","d"]) → ("a", "c")(一次取多个位置组成元组、用于多级排序 sorted(data, key=itemgetter("dept", "salary")) 先按 dept 再按 salary)。最典型:sorted/max/min 的 key(按元组第 2 项排序 key=itemgetter(1)、找 score 最高 max(records, key=itemgetter("score")))。vs lambda:key=itemgetter(1) 更快更明确、key=lambda x: x[1] 慢一点。支持任何 __getitem__ 的对象(列表、元组、字典、字符串)。理解「itemgetter(k)取 objk;取多个返回元组(多级排序);排序/max/min 的 key 最常用;比 lambda 快;支持任何__getitem__对象」,就掌握了 itemgetter。
三、attrgetter:按属性取值
理解 attrgetter:
attrgetter('name'):返回函数,取 obj.name
f = attrgetter("age")
f(person) → person.age
嵌套属性(属性链):
attrgetter("addr.city")(user) → user.addr.city
→ 点号分隔,自动逐级取
取多个属性:
attrgetter("name", "age")(person) → (person.name, person.age)
最典型:对象列表排序
# 按对象的 age 属性排序
sorted(people, key=attrgetter("age"))
# 多级:先按 dept 再按 salary
sorted(emps, key=attrgetter("dept", "salary"))
# 找最年长的
max(people, key=attrgetter("age"))
vs lambda:
key=attrgetter("age") # 明确、快
key=lambda p: p.age # 慢、啰嗦
itemgetter vs attrgetter(关键区分):
itemgetter → 用于"下标/键"(x[k]):列表、字典、元组
attrgetter → 用于"属性"(x.attr):对象
→ 数据是字典用 itemgetter("key")、是对象用 attrgetter("attr")
常见混淆:
字典的值:itemgetter("age")(不是 attrgetter!字典不是属性)
对象的字段:attrgetter("age")
所以 attrgetter('a')取 obj.a(属性),支持嵌套 a.b、取多个;对象排序 key 用它
attrgetter('name'):返回函数、取 obj.name——f = attrgetter("age")、f(person) → person.age。嵌套属性(属性链):attrgetter("addr.city")(user) → user.addr.city(点号分隔、自动逐级取)。取多个属性:attrgetter("name", "age")(person) → (person.name, person.age)。最典型:对象列表排序(sorted(people, key=attrgetter("age"))、多级 attrgetter("dept", "salary")、max(people, key=attrgetter("age")))。itemgetter vs attrgetter(关键区分):itemgetter 用于「下标/键」(x[k]):列表、字典、元组;attrgetter 用于「属性」(x.attr):对象——数据是字典用 itemgetter("key")、是对象用 attrgetter("attr")。常见混淆:字典的值用 itemgetter("age")(不是 attrgetter!字典不是属性)、对象的字段用 attrgetter("age")。理解「attrgetter(‘a’)取 obj.a 属性;支持嵌套 a.b、取多个;对象排序 key 用它;★区分:itemgetter 用于下标/键(列表/字典)、attrgetter 用于属性(对象);字典用 itemgetter、对象用 attrgetter」,就掌握了 attrgetter。
四、methodcaller 与运算符函数
理解 methodcaller 和运算符函数:
methodcaller('method', *args, **kwargs):调用 obj.method(args)
f = methodcaller("upper")
f("hello") → "HELLO"(调 "hello".upper())
f = methodcaller("replace", "a", "b")
f("banana") → "bbnbnb"(调 s.replace("a","b"))
→ 带参数的方法调用也能封装
用途:
sorted(words, key=methodcaller("lower")) # 按小写排序
map(methodcaller("strip"), lines) # 每行 strip
filter(methodcaller("startswith", "http"), urls)
vs lambda:
methodcaller("lower") # 明确
lambda s: s.lower() # 等价
运算符函数(operator.xxx):
算术:add, sub, mul, truediv, floordiv, mod, pow, neg
比较:lt, le, eq, ne, ge, gt
逻辑/位:and_, or_, xor, not_, inv
序列:getitem, setitem, delitem, contains, concat
就地:iadd, imul...(+=、*= 对应)
用途——把运算符当函数传:
reduce(operator.mul, nums) # 连乘
reduce(operator.add, lists, []) # 拼接列表(虽然有更好写法)
max(a, b, key=...)
sorted(pairs, key=..., reverse=...)
# operator.itemgetter 配 sorted 二级
# operator.and_ 配 reduce 求交集
什么时候用运算符函数:
✓ 需要"把运算符作为函数传递"(reduce/map 里)
✗ 普通运算直接用 +/*(别 operator.add(a,b) 代替 a+b)
所以 methodcaller('m',args)调 obj.m(args);运算符函数(add/mul/lt)把运算符当函数传
methodcaller('method', *args, **kwargs):调用 obj.method(args)——methodcaller("upper")("hello") → “HELLO”、methodcaller("replace", "a", "b")("banana") → “bbnbnb”(带参数的方法调用也能封装)。用途:sorted(words, key=methodcaller("lower"))、map(methodcaller("strip"), lines)、filter(methodcaller("startswith", "http"), urls)。运算符函数(operator.xxx):算术(add/sub/mul/truediv/mod/pow)、比较(lt/le/eq/ne/ge/gt)、逻辑/位(and_/or_/not_)、序列(getitem/contains/concat)、就地(iadd/imul)。用途——把运算符当函数传:reduce(operator.mul, nums) 连乘、reduce(operator.add, lists, [])。什么时候用运算符函数:需要「把运算符作为函数传递」时用(reduce/map 里)、普通运算直接用 +/*(别用 operator.add(a,b) 代替 a+b)。理解「methodcaller(‘m’,args)调 obj.m(args)(带参数);运算符函数 add/mul/lt 把运算符当函数传(reduce/map 里);普通运算直接用运算符别用 operator」,就掌握了 methodcaller 和运算符函数。
五、配合 sorted/groupby/max
理解 operator 在这些函数里的实战:
配合 sorted(最常用):
# 单级
sorted(data, key=itemgetter(1))
sorted(objs, key=attrgetter("age"))
# 多级(一次取多个)
sorted(data, key=itemgetter("dept", "salary"))
# 降序
sorted(data, key=itemgetter(1), reverse=True)
# 混合升降序:用负号或多次 sort(稳定排序)
data.sort(key=itemgetter("name"))
data.sort(key=itemgetter("age"), reverse=True) # 后排的优先级高
配合 max/min:
max(records, key=itemgetter("score")) # score 最高的记录
min(people, key=attrgetter("age")) # 最年轻的人
配合 itertools.groupby(必须先排序!):
from itertools import groupby
data.sort(key=itemgetter("dept")) # 先按分组键排序
for dept, group in groupby(data, key=itemgetter("dept")):
print(dept, list(group))
⚠️ groupby 只分组"相邻的相同键",不排序会分错
配合 map/filter:
map(methodcaller("upper"), strings)
filter(itemgetter("active"), records) # 保留 active 为真的
为什么这些场景 operator 好:
① 更快(C 实现,无 Python lambda 调用开销)
② 更明确(itemgetter(1) 一眼是"取第 1 项")
③ 多级排序 itemgetter(a, b) 比 lambda x: (x[a],x[b]) 简洁
所以 operator 配 sorted/max/groupby/map:更快更明确,多级排序 itemgetter(a,b)简洁
配合 sorted(最常用):单级 sorted(data, key=itemgetter(1))、多级 itemgetter("dept", "salary")、降序 reverse=True、混合升降序用多次 sort(稳定排序、后排的优先级高)。配合 max/min:max(records, key=itemgetter("score"))、min(people, key=attrgetter("age"))。配合 itertools.groupby(必须先排序!):data.sort(key=itemgetter("dept")) 后 groupby(data, key=itemgetter("dept"))(groupby 只分组相邻的相同键、不排序会分错)。配合 map/filter:map(methodcaller("upper"), strings)、filter(itemgetter("active"), records)。为什么这些场景 operator 好:① 更快(C 实现)、② 更明确、③ 多级排序 itemgetter(a, b) 比 lambda 简洁。理解「operator 配 sorted(单/多级/降序)/max-min/groupby(必须先排序)/map-filter;更快更明确;多级排序 itemgetter(a,b)简洁;groupby 只分组相邻相同键要先排序」,就掌握了实战配合。
六、总结与实践
总结 operator 模块:
三个取值工厂(最常用):
itemgetter(k) 取 x[k](下标/键)→ 列表、字典、元组
attrgetter(a) 取 x.a(属性)→ 对象
methodcaller(m) 调 x.m()(方法)
→ 都能取多个 / 带参数(methodcaller)
运算符函数:
operator.add/mul/sub/lt/eq... 把运算符当函数传
→ 用于 reduce/map(需要传函数时)
最常用场景:
sorted/max/min 的 key(比 lambda 快、明确)
多级排序 itemgetter/attrgetter(a, b)
groupby(先排序,key=itemgetter)
reduce(operator.mul 等)
关键区分:
下标/键 → itemgetter(字典的值用它,不是 attrgetter)
属性 → attrgetter
方法 → methodcaller
vs lambda:
operator:更快(C 实现)、更明确
lambda:更灵活(任意逻辑)
→ 纯"取值/运算符"用 operator、复杂逻辑用 lambda
核心总结:
operator 把运算符/取值变函数
itemgetter 取下标键、attrgetter 取属性、methodcaller 调方法
做 sorted/max/groupby 的 key,比 lambda 快且清晰
所以 operator:itemgetter 取下标键/attrgetter 取属性/methodcaller 调方法,key 比 lambda 快
三个取值工厂(最常用):itemgetter(k)(取 x[k] 下标/键、列表/字典/元组)、attrgetter(a)(取 x.a 属性、对象)、methodcaller(m)(调 x.m() 方法)——都能取多个/带参数。运算符函数:operator.add/mul/lt 把运算符当函数传(用于 reduce/map)。最常用场景:sorted/max/min 的 key、多级排序、groupby(先排序)、reduce。关键区分:下标/键用 itemgetter(字典的值用它不是 attrgetter)、属性用 attrgetter、方法用 methodcaller。vs lambda:operator 更快更明确、lambda 更灵活(纯取值/运算符用 operator、复杂逻辑用 lambda)。理解「operator:itemgetter 取下标键/attrgetter 取属性/methodcaller 调方法;运算符函数把运算符当函数传;做 sorted/max/groupby 的 key 比 lambda 快明确;字典用 itemgetter、对象用 attrgetter」,就掌握了总结与实践。
记忆钩子:「operator 模块把 Python 的『运算符和取值操作』封装成『函数』,这样运算符/取值也能当参数传(key/reduce 的二元函数),比 lambda 更快(C 实现)更明确;★三个取值工厂:①itemgetter(k)返回函数取 objk,itemgetter(1)≈lambda x:x[1],能取多个 itemgetter(0,2)返回元组(用于多级排序)→用于列表/字典/元组②attrgetter(‘name’)取 obj.name(属性),支持嵌套 attrgetter(‘a.b’)、取多个→用于对象③methodcaller(‘upper’)调 obj.upper()(方法),能带参数 methodcaller(‘replace’,‘a’,‘b’)→调方法;★关键区分:下标/键用 itemgetter(字典的值用 itemgetter(‘key’)不是 attrgetter!)、属性用 attrgetter、方法用 methodcaller;★最典型用途是排序 key:sorted(people,key=attrgetter(‘age’))、sorted(data,key=itemgetter(1))、max(records,key=itemgetter(‘score’))、配 itertools.groupby(必须先按相同 key 排序);运算符函数 operator.add/mul/lt/eq 把+/*/</==变成函数,用于 reduce(operator.mul,nums)连乘等需要传函数的场景(普通运算直接用运算符别用 operator.add)」。
七、常见误区与追问
- 误区:字典按值排序要用 attrgetter。 用 itemgetter——字典是用「键」取值(
d["age"]、走__getitem__),所以sorted(dicts, key=itemgetter("age"));attrgetter 是取「属性」(obj.age、走.),用于对象;常见混淆是把字典的键当成属性,记住:下标/键(列表、字典、元组)用 itemgetter,对象的字段/属性用 attrgetter。 - 误区:itemgetter 和 attrgetter 只能取一个值。 都能取多个——
itemgetter(0, 2)返回(x[0], x[2])元组、attrgetter("name", "age")返回(x.name, x.age)元组;这正好用于「多级排序」:sorted(data, key=itemgetter("dept", "salary"))会先按 dept 排、dept 相同再按 salary 排(元组比较是逐项比较),比lambda x: (x["dept"], x["salary"])更简洁。 - 误区:operator 模块只是 lambda 的替代,没必要用。 有实际优势——① 更快:itemgetter/attrgetter 是 C 实现的,没有 Python 层 lambda 的函数调用开销,在排序大数据时差异明显;② 更明确:
itemgetter(1)一眼看出「取第 1 项」、attrgetter("age")一眼看出「取 age 属性」,比lambda x: x[1]意图更清晰;③ 支持多级取值(取元组);所以在 sorted/max/groupby 的 key 这类高频场景,operator 是更好的选择。 - 误区:groupby 不用排序也能正确分组。 不能——
itertools.groupby只把「相邻的、键相同的」元素分到一组,遇到键变化就开新组;如果数据没按分组键排序,相同键的元素分散在各处,会被分成多个组(分错);所以用 groupby 前必须先按分组键排序:data.sort(key=itemgetter("dept")); groupby(data, key=itemgetter("dept"));这是 groupby 最常见的坑。 - 追问:itemgetter、attrgetter、methodcaller 分别在什么场景用? 按「怎么取值」区分:① itemgetter(k) 用于「按下标或键取值」(
obj[k])——列表/元组按下标itemgetter(1)、字典按键itemgetter("age")、字符串按下标;② attrgetter(“attr”) 用于「按属性取值」(obj.attr)——对象的字段,支持嵌套属性链attrgetter("addr.city");③ methodcaller(“method”, args) 用于「调用对象的方法」(obj.method(args))——如methodcaller("lower")调 str.lower()、methodcaller("get", "key")调 dict.get();三者最常见的用途都是给 sorted/max/min/groupby/map 传 key 或映射函数,比写 lambda 更快更明确;简单记:下标键用 itemgetter、属性用 attrgetter、方法用 methodcaller。 - 追问:operator 里的运算符函数(如 operator.mul)什么时候用? 主要在「需要把运算符本身当作函数传递」的场景用,最典型是
functools.reduce——reduce(operator.mul, nums)做连乘、reduce(operator.add, lists, [])拼接(虽然拼接有更好写法)、reduce(operator.or_, sets)求集合并集;此外 sorted/max 的比较、需要动态选择运算的场合也可能用到;但要注意:普通的算术运算直接写运算符就好(a + b而不是operator.add(a, b)),operator 的运算符函数只在「运算符不能直接写、必须传一个函数进去」时才有意义;求和、求积、极值这些有专门内置(sum、math.prod、max/min)时优先用内置,比 reduce + operator 更清晰。 - 追问:怎么用 operator 做「先按部门升序、再按薪资降序」这种混合排序? 因为 Python 的 sort 是稳定排序(相等元素保持原顺序),可以「分多次排序、从次要键到主要键」:先按次要键排、再按主要键排,最后主要键的顺序占主导、主要键相同的保持次要键的顺序;「先按部门升序、再按薪资降序」写成:
data.sort(key=itemgetter("salary"), reverse=True)(先按薪资降序),再data.sort(key=itemgetter("dept"))(再按部门升序)——最终部门升序、同部门内薪资降序;如果所有键都同方向(都升或都降),可以一次搞定sorted(data, key=itemgetter("dept", "salary"))(取元组多级比较);只有「不同键不同方向」时才需要分多次 sort(利用稳定性)。
八、加强记忆
operator 模块把 Python 的「运算符和取值操作」封装成「函数」,这样运算符/取值也能当参数传(key/reduce 的二元函数),比 lambda 更快(C 实现)更明确。三个取值工厂:① itemgetter(k) 返回函数取 obj[k](下标或键,itemgetter(1) ≈ lambda x: x[1],能取多个 itemgetter(0, 2) 返回元组用于多级排序)→ 用于列表/字典/元组;② attrgetter('name') 取 obj.name(属性,支持嵌套 attrgetter('a.b')、取多个)→ 用于对象;③ methodcaller('upper') 调 obj.upper()(方法,能带参数 methodcaller('replace', 'a', 'b'))→ 调方法。关键区分:下标/键用 itemgetter(字典的值用 itemgetter("key") 不是 attrgetter)、属性用 attrgetter、方法用 methodcaller。最典型用途是排序 key:sorted(people, key=attrgetter("age"))、sorted(data, key=itemgetter(1))、max(records, key=itemgetter("score"))、配 itertools.groupby(必须先按相同 key 排序)。运算符函数 operator.add/mul/lt/eq 把 +/*/</== 变成函数,用于 reduce(operator.mul, nums) 等需要传函数的场景(普通运算直接用运算符)。一句话「operator 把运算符/取值变函数;itemgetter 取下标键(列表/字典)、attrgetter 取属性(对象)、methodcaller 调方法;做 sorted/max/groupby 的 key 比 lambda 快且明确、能取多个做多级排序;运算符函数用于 reduce 等传函数场景」。