第 14 课 · 入门 · 19 分钟

Python 排序与 key 函数

按照真正关心的字段排序,而不是只对一串数字排序。

sorted 与 sortkey 函数zip 与 enumerate

订单该按付款顺序离开柜台,加急的除外。按一个字段排序不难,按两个字段就要用到元组了。哪个字段该排在前面,为什么?

排序本身不难,难的是记录有多个字段时该按什么排。Python 允许传入 key 函数,把每个元素转换成用于比较的值。这样既保留原记录,也让排序规则变得明确。 你还会用稳定排序叠加规则、用元组键混合升降序,并用 min/max 代替只为取一个值的排序。

sorted 返回新列表

sorted(items) 会创建新的排序列表,原列表不变。items.sort() 原地排序并返回 None。还需要保留原顺序时,用 sorted。

prices = [6.0, 4.5, 3.25]
print(sorted(prices))
print(prices)

新的排序列表

用 key 排序记录

key 接收一个元素,返回 Python 应该比较的值。lambda 适合写很短的规则。这里的第二个字段 price 就是排序键。

orders = [("latte", 4.5), ("tea", 3.0), ("mocha", 5.0)]
for name, price in sorted(orders, key=lambda item: item[1]):
    print(name, price)

按价格排序元组

enumerate 与 zip

enumerate 同时给出索引和值。zip 把两个序列的元素配对。需要位置或并行数据时,它们能让循环保持清楚。

names = ["latte", "tea"]
prices = [4.5, 3.0]
for i, (name, price) in enumerate(zip(names, prices), 1):
    print(i, name, price)

索引与配对数据

稳定排序可以叠加规则

Python 的排序是稳定的:键相同的元素保持之前的相对顺序。先按次要规则排序,再按主要规则排序;第二次排序遇到相同主键时,会保留第一次的顺序。

orders = [
    {"name": "tea", "price": 3.0},
    {"name": "latte", "price": 4.5},
    {"name": "mocha", "price": 4.5},
]
by_name = sorted(orders, key=lambda order: order["name"])
by_price = sorted(by_name, key=lambda order: order["price"], reverse=True)
for order in by_price:
    print(order["name"], order["price"])

先次键,后主键

元组键混合字段,不混合方向

元组键从左到右比较。(-price, name) 让价格降序、名称升序,因为负号只反转了价格这个字段。reverse=True 会把两个字段一起反转,通常不是想要的结果。

orders = [("latte", 4.5), ("tea", 3.0), ("mocha", 4.5)]
for name, price in sorted(orders, key=lambda item: (-item[1], item[0])):
    print(name, price)

用计算出的键只反转一个字段

只取一个记录时不必排序

只需要最便宜的一项时,min() 配 key 比完整排序更清楚,也更省。max() 同理。key 里的规则要和排序时使用的规则完全一致。

orders = [("latte", 4.5), ("tea", 3.0), ("mocha", 5.0)]
cheapest = min(orders, key=lambda item: item[1])
priciest = max(orders, key=lambda item: item[1])
print(cheapest)
print(priciest)

取极值不需要完整顺序

本课要点

  • sorted 返回新列表,sort 原地修改列表。
  • key 决定按哪个值比较。
  • reverse=True 从大到小排序。
  • enumerate 加索引,zip 并行遍历两个序列。
  • 稳定排序可以组合;元组键能只反转一个字段。

常见错误

  • 把 sort() 的返回值赋回列表,结果得到 None。
  • 调用 key 函数而不是把函数本身传进去。
  • 以为 zip 会在较长序列结束后继续。
  • 用了 reverse=True,却期待次要字段仍然升序。
轮到你

按价格排序三个 (name, price) 元组,再打印每个名称和价格。

动手试试

改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。

浏览器内 Python本地运行 · 无需账号
首次运行会从 CDN 下载 Pyodide。修改后点击“运行代码”。

练习本课

12 道题,作答后立即看到解释。

查看 12 道题答案开始练习
← 推导式类与对象 →