返回博客

21. sorted 与匿名函数 lambda:灵活排序的利器

21. sorted 与匿名函数 lambda:灵活排序的利器

第二十一篇:sorted 与匿名函数 lambda——灵活排序的利器

前言

**lambda(匿名函数)**和 sorted() 是 Python 中处理数据排序与转换的两大神器。本文全面讲解 lambda 语法、sorted() 的高级用法,以及两者配合实现复杂排序的技巧。

lambda 基础

基本语法

# 普通函数 vs lambda
def add(a, b):
    return a + b

add_lambda = lambda a, b: a + b
print(add_lambda(3, 5))  # 8

# ⚠️ lambda 只能包含表达式(不能含语句)
square = lambda x: x ** 2
print(square(4))  # 16

lambda 作为一等公民

ops = {
    "+": lambda a, b: a + b,
    "-": lambda a, b: a - b,
    "*": lambda a, b: a * b,
    "/": lambda a, b: a / b if b != 0 else float('inf'),
}

result = ops["+"](10, 5)
print(result)  # 15

operation = "*"
result = ops[operation](10, 5)
print(result)  # 50

lambda 在 map/filter/reduce 中的应用

nums = [1, 2, 3, 4, 5]

# map — 平方
squares = list(map(lambda x: x ** 2, nums))
print(squares)  # [1, 4, 9, 16, 25]

# filter — 偶数
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)  # [2, 4]

from functools import reduce
product = reduce(lambda a, b: a * b, nums)
print(product)  # 120

lambda 的局限性

# ❌ Lambda 只支持单行表达式
bad_example = lambda: (x := 5; print(x); return x * 2)  # SyntaxError!

# ✅ 多行逻辑应提取为具名函数
def complex_transform(x):
    result = x * 2
    result += 10
    return max(result, 100)

# Lambda 适合简单的一行转换
double_plus_ten = lambda x: x * 2 + 10
print(double_plus_ten(5))  # 20

sorted() 函数详解

基本用法

nums = [5, 2, 8, 1, 9, 3]

# 正序排序
print(sorted(nums))           # [1, 2, 3, 5, 8, 9]

# 逆序排序
print(sorted(nums, reverse=True))  # [9, 8, 5, 3, 2, 1]

# 不修改原列表
print(nums)                  # [5, 2, 8, 1, 9, 3] — 不变

字符串排序

words = ["banana", "Apple", "cherry", "date"]

# 默认按字典序
print(sorted(words))          # ['Apple', 'banana', 'cherry', 'date']

# 忽略大小写
print(sorted(words, key=str.lower))   # ['apple', 'banana', 'cherry', 'date']

# 按长度排序
print(sorted(words, key=len))         # ['date', 'Apple', 'banana', 'cherry']

字典排序

students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 95},
    {"name": "Carol", "score": 78},
]

# 按分数升序
print(sorted(students, key=lambda s: s["score"]))
# Carol(78), Alice(85), Bob(95)

# 按名字字母序
print(sorted(students, key=lambda s: s["name"].upper()))
# Alice, Bob, Carol

# 多级排序:先分数降序,再姓名升序
print(sorted(students, key=lambda s: (-s["score"], s["name"])))

自定义比较键

class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.height = height
    
    def __repr__(self):
        return f"Person({self.name}, {self.age}cm)"

persons = [
    Person("张三", 25, 170),
    Person("李四", 30, 180),
    Person("王五", 22, 165),
]

# 按身高排序
print(sorted(persons, key=lambda p: p.height))
# [王五 (22, 165cm), 张三 (25, 170cm), 李四 (30, 180cm)]

# 按年龄/身高的复合键
print(sorted(persons, key=lambda p: (p.age, -p.height)))

sorted 与 list.sort() 对比

特性 list.sort() sorted(iterable)
返回值 None(原地排序) 新列表(原 iterable 不变)
适用对象 仅列表 任何可迭代对象
内存效率 更高(就地操作) 需要额外内存复制
链式调用 ❌ 不能(返回 None) ✅ 可以
nums = [3, 1, 2]

# sort() 原地排序,返回 None
result_sort = nums.sort()
print(result_sort)     # None
print(nums)            # [1, 2, 3]

# sorted() 返回新列表
result_sorted = sorted([3, 1, 2])
print(result_sorted)   # [1, 2, 3]
print([3, 1, 2])       # [3, 1, 2] — 不变

高级排序技巧

稳定排序与多重键

records = [
    ("Alice", 95, 170),
    ("Bob", 95, 180),
    ("Carol", 85, 165),
    ("Dave", 85, 175),
]

# 按成绩排序(稳定排序:相同成绩保持原始顺序)
stable_sorted = sorted(records, key=lambda r: r[1], reverse=True)
for record in stable_sorted:
    print(f"{record[0]}: 成绩={record[1]}, 身高={record[2]}")
# Alice(95), Bob(95), Dave(85), Carol(85) — 同分顺序不变

# 复合键:第一关键字成绩,第二关键字身高
compound_sorted = sorted(records, key=lambda r: (-r[1], -r[2]))
for record in compound_sorted:
    print(f"{record[0]}: 成绩={record[1]}, 身高={record[2]}")
# Bob(95, 180), Alice(95, 170), Dave(85, 175), Carol(85, 165)

利用 operator.itemgetter 优化

from operator import itemgetter, attrgetter

students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 95},
    {"name": "Carol", "score": 78},
]

# 用 itemgetter 替代 lambda(速度略快)
sorted_by_score = sorted(students, key=itemgetter("score"))

# 多级排序
multi_key = sorted(students, key=itemgetter("score", "name"))

# 对象属性排序
persons = [Person("A", 25, 170), Person("B", 30, 180)]
sorted_by_attr = sorted(persons, key=attrgetter("height"))

自定义排序规则

# 按数字的绝对值排序
numbers = [-5, -3, 0, 3, 5, -1, 1]
print(sorted(numbers, key=abs))
# [0, -1, 1, -3, 3, -5, 5]

# 按最后一个字母排序
words = ["hello", "world", "python", "java"]
print(sorted(words, key=lambda w: w[-1]))
# java, hello, world, python

# 按出现频率排序
text = "aabbbccccddddd"
from collections import Counter
freq = Counter(text)
# {'d': 5, 'c': 4, 'b': 3, 'a': 2}
by_freq = sorted(freq.items(), key=lambda x: x[1], reverse=True)
print(by_freq)
# [('d', 5), ('c', 4), ('b', 3), ('a', 2)]

自定义类排序

class Score:
    def __init__(self, name, math, english, chinese):
        self.name = name
        self.math = math
        self.english = english
        self.chinese = chinese
    
    @property
    def total(self):
        return self.math + self.english + self.chinese
    
    def __repr__(self):
        return f"{self.name} 总分={self.total}"

scores = [
    Score("张三", 90, 85, 92),
    Score("李四", 88, 92, 78),
    Score("王五", 95, 80, 85),
]

# 按总分排序
total_sorted = sorted(scores, key=lambda s: s.total, reverse=True)
for score in total_sorted:
    print(score)

lambda 常见陷阱

陷阱一:闭包延迟绑定

# ❌ 错误:所有函数都捕获相同的 i(最终值为 4)
funcs = []
for i in range(5):
    funcs.append(lambda: i)

for f in funcs:
    print(f())
# 全部输出 4!(不是预期的 0,1,2,3,4)

# ✅ 修复方案一:使用默认参数捕获当时的值
funcs = []
for i in range(5):
    funcs.append(lambda x=i: x)  # 默认参数在定义时求值

for f in funcs:
    print(f())
# 0 1 2 3 4 — 正确!

# ✅ 修复方案二:生成器推导式自动捕获
funcs_gen = (lambda i=i: i for i in range(5))
for f in funcs_gen:
    print(f())

陷阱二:Lambda 不支持赋值

# ❌ Lambda 内不能有语句
wrong = lambda: (x := 5; print(x))  # SyntaxError!

# ✅ 改用多行函数
def my_func():
    x = 5
    print(x)
    return x

实战示例

学生成绩排名系统

class StudentDatabase:
    def __init__(self, students_data):
        self.students = [Student(*data) for data in students_data]
    
    def get_top_students(self, n=3, subject="math"):
        """获取某科目最高分的 n 名学生"""
        key_map = {
            "math": lambda s: s.math,
            "english": lambda s: s.english,
            "chinese": lambda s: s.chinese,
            "total": lambda s: s.total,
        }
        key_fn = key_map.get(subject, lambda s: s.total)
        return sorted(self.students, key=key_fn, reverse=True)[:n]
    
    def rank_all(self):
        """按总分排名并打印"""
        ranked = sorted(self.students, key=lambda s: s.total, reverse=True)
        for i, student in enumerate(ranked, 1):
            print(f"Rank{i}. {student}")

class Student:
    def __init__(self, name, math, english, chinese):
        self.name = name
        self.math = math
        self.english = english
        self.chinese = chinese
    
    @property
    def total(self):
        return self.math + self.english + self.chinese
    
    def __repr__(self):
        return f"{self.name} 总分={self.total}"

db = StudentDatabase([
    ("张三", 90, 85, 92),
    ("李四", 88, 92, 78),
    ("王五", 95, 80, 85),
])
db.rank_all()
# Rank1. 王五 总分=260
# Rank2. 张三 总分=267
# Rank3. 李四 总分=258

日志解析与分组统计

import re
from collections import defaultdict

log_lines = [
    "2024-01-15 INFO user_login alice",
    "2024-01-15 ERROR db_connection failed",
    "2024-01-16 INFO file_upload bob",
    "2024-01-16 ERROR api_timeout server1",
]

# 解析日志并按类型分组
parsed = [re.match(r"(.*?) (\w+) (.*)", line).groups() for line in log_lines]
grouped = defaultdict(list)
for date, level, msg in parsed:
    grouped[(date, level)].append(msg)

# 按日期和时间先后排序
sorted_entries = sorted(grouped.keys(), key=lambda x: (x[0], x[1]))
for key in sorted_entries:
    print(f"{key}: {', '.join(grouped[key])}")

总结

本文涵盖了 sorted 与 lambda 的核心知识:

  1. lambda 语法 — 匿名单行函数,适合简单转换
  2. lambda 作为函数传递 — 配合 map/filter/reduce 使用
  3. sorted() 基础 — 正序/逆序、不修改原对象
  4. key 参数 — 自定义排序规则的核心机制
  5. 多级排序 — tuple/元组作为复合键
  6. operator 工具 — itemgetter/attrgetter 替代 lambda 加速
  7. 自定义类排序 — @property + key 组合
  8. 常见陷阱 — 闭包延迟绑定、不支持语句
  9. 性能对比 — C 层 key 函数比 Python 循环快
  10. 实战应用 — 学生排名系统、日志解析分组

下一篇文章将深入讲解返回函数与闭包。


本文是 Python 教程系列的第二十一篇。