第3章 3.2 数组函数 20 个高阶用法
上章我们玩转了索引数组和关联数组这两种基本容器,小明的成绩单已经能存能取了。但老板/老师要是跟你说:"把不及格的分数标红"、"把成绩按高低排个名"、"算一下每个学科的平均分"——这些批量操作,光靠 for 循环你就得写半天。
这节课,我们来学 20 个数组高阶函数,让你的代码少写 80% 的 for 循环,而且更不容易出错。
🎯 开场 3 分钟:为什么要学这个?
场景还原:你是班主任,要处理全班 50 人的成绩:
scores = {
"语文": [78, 82, 60, 90, 55, 88],
"数学": [92, 85, 71, 88, 45, 95],
"英语": [81, 77, 83, 90, 62, 86]
}
老板问你:
- 哪科有人不及格?
- 每科平均分多少?
- 把三科成绩合并算总分排名?
不用高阶函数:你得写 3 层嵌套 for 循环,调试半天
用了高阶函数:10 行代码搞定,而且逻\n\n
\n\n
\n\n辑一目了然
学完这章,你遇到"批量处理"的需求,第一反应不再是 for,而是 map/filter/reduce 三剑客。
🧱 基础 25 分钟:核心概念
什么是高阶函数?—— 数据的"流水线工人"
普通函数是:进来一个东西,出去一个东西
def 加工(原材料):
return 成品
高阶函数是:你告诉它用什么加工程序,它自动流水线作业
# 普通函数:单件加工
def double(x):
return x * 2
# 高阶函数 map:一批货自动流水线
result = list(map(double, [1, 2, 3, 4, 5]))
print(result) # [2, 4, 6, 8, 10]
类比:普通函数是一个厨师做一个菜,高阶函数是中央厨房的流水线,你设定好加工程序,原料自动流过去,出来的就是成品。
map() —— 批量加工
干什么用:对数组里每个元素执行同一个操作
# 场景:把所有成绩都转换成"及格/不及格"标签
scores = [78, 82, 60, 90, 55, 88]
def to_pass_status(score):
return "及格" if score >= 60 else "不及格"
statuses = list(map(to_pass_status, scores))
print(statuses) # ['及格', '及格', '不及格', '及格', '不及格', '及格']
map(函数, 数组):把数组里每个元素丢给函数加工,返回加工结果
filter() —— 自动筛选
干什么用:挑出数组里满足条件的元素
# 场景:找出不及格的同学
scores = [78, 82, 60, 90, 55, 88]
def is_failing(score):
return score < 60
failing = list(filter(is_failing, scores))
print(failing) # [55]
filter(条件函数, 数组):只保留让条件函数返回 True 的元素
reduce() —— 累计计算
干什么用:把数组元素合并成一个结果
# 场景:算总分
from functools import reduce
scores = [78, 82, 60, 90, 55, 88]
def sum_total(acc, score):
return acc + score
total = reduce(sum_total, scores, 0) # 0 是初始值
print(total) # 453
reduce(合并函数, 数组, 初始值):从左到右两两合并
匿名函数 lambda —— 不需要取名字的临时工
如果每个高阶函数都要先定义一个函数,代码会很繁琐。lambda 让你临时写一个匿名函数:
# 不用 lambda
def double(x):
return x * 2
# 用 lambda(一行搞定)
double = lambda x: x * 2
result = list(map(lambda x: x * 2, [1, 2, 3, 4, 5]))
print(result) # [2, 4, 6, 8, 10]
语法:lambda 参数: 返回值
zip() —— 并行配对
干什么用:把多个数组合并成一对一配对
# 场景:把名字和分数配对
names = ["小明", "小红", "小李"]
scores = [85, 92, 78]
paired = list(zip(names, scores))
print(paired) # [('小明', 85), ('小红', 92), ('小李', 78)]
enumerate() —— 带编号的循环
干什么用:循环数组时同时拿到索引和值
names = ["小明", "小红", "小李"]
for i, name in enumerate(names, start=1): # start=1 让编号从1开始
print(f"第{i}名: {name}")
sorted() + key —— 自定义排序规则
干什么用:按你指定的规则排序
# 场景:按成绩高低排名字
students = [("小明", 85), ("小红", 92), ("小李", 78)]
# 按分数降序排
ranked = sorted(students, key=lambda x: x[1], reverse=True)
print(ranked) # [('小红', 92), ('小明', 85), ('小李', 78)]
list comprehension 列表推导式 —— 升级版 map
# 传统 map
result = list(map(lambda x: x * 2, [1, 2, 3, 4, 5]))
# 列表推导式(更 Pythonic)
result = [x * 2 for x in [1, 2, 3, 4, 5]]
print(result) # [2, 4, 6, 8, 10]
列表推导式 = 简化版的 map + filter 组合
any() / all() —— 批量判断
scores = [78, 82, 60, 90, 55, 88]
print(any(s < 60 for s in scores)) # True 有人不及格吗?
print(all(s >= 60 for s in scores)) # False 所有人都及格了吗?
set() + zip() 找共同点
# 场景:小明和小红都选了哪些课?
ming_courses = ["语文", "数学", "英语", "物理"]
hong_courses = ["数学", "英语", "化学", "生物"]
common = set(ming_courses) & set(hong_courses)
print(common) # {'数学', '英语'}
🔥 实战 35 分钟:3 个递进小项目
项目 1:成绩分析器(5 分钟)
# 成绩分析器 - 5分钟搞定
scores = [78, 82, 60, 90, 55, 88]
# 1. 找出不及格的
failing = list(filter(lambda x: x < 60, scores))
# 2. 算平均分
average = sum(scores) / len(scores)
# 3. 所有人及格了吗?
all_passed = all(s >= 60 for s in scores)
print(f"不及格人数: {len(failing)}, 分数: {failing}")
print(f"平均分: {average:.1f}")
print(f"全部及格: {all_passed}")
预期输出:
不及格人数: 1, 分数: [55]
平均分: 75.5
全部及格: False
项目 2:处理真实数据 —— 班级成绩单(15 分钟)
# 班级成绩单 - 从字典列表读取和处理
class_scores = [
{"name": "小明", "chinese": 78, "math": 92, "english": 85},
{"name": "小红", "chinese": 82, "math": 88, "english": 90},
{"name": "小李", "chinese": 60, "math": 71, "english": 62},
{"name": "小王", "chinese": 55, "math": 45, "english": 58},
{"name": "小张", "chinese": 88, "math": 95, "english": 86},
]
# 1. 给每人算总分
def calc_total(student):
return {
"name": student["name"],
"total": student["chinese"] + student["math"] + student["english"]
}
students_with_total = list(map(calc_total, class_scores))
print("=== 每位同学总分 ===")
for s in students_with_total:
print(f"{s['name']}: {s['total']}分")
# 2. 找出不及格的同学(任意一科<60)
def has_fail(student):
return student["chinese"] < 60 or student["math"] < 60 or student["english"] < 60
failing_students = list(filter(has_fail, class_scores))
print(f"\n=== 需要补考的同学: {[s['name'] for s in failing_students]} ===")
# 3. 按总分排名
ranked = sorted(students_with_total, key=lambda x: x["total"], reverse=True)
print("\n=== 总分排名 ===")
for i, s in enumerate(ranked, 1):
print(f"第{i}名: {s['name']} - {s['total']}分")
预期输出:
=== 每位同学总分 ===
小明: 255分
小红: 260分
小李: 193分
小王: 158分
小张: 269分
=== 需要补考的同学: ['小李', '小王'] ===
=== 总分排名 ===
第1名: 小张 - 269分
第2名: 小红 - 260分
第3名: 小明 - 255分
第4名: 小李 - 193分
第5名: 小王 - 158分
运行提示:直接复制粘贴就能跑,数据结构换成从 CSV/JSON 读取也一样
项目 3:实战小工具 —— 待办清单管理器(15 分钟)
# 待办清单管理器 - 组合多个数组函数
from datetime import datetime
# 初始任务列表
tasks = [
{"id": 1, "content": "完成数学作业", "priority": "高", "done": False, "deadline": "2024-01-15"},
{"id": 2, "content": "给妈妈打电话", "priority": "中", "done": True, "deadline": "2024-01-14"},
{"id": 3, "content": "背英语单词", "priority": "低", "done": False, "deadline": "2024-01-20"},
{"id": 4, "content": "整理房间", "priority": "中", "done": False, "deadline": "2024-01-16"},
{"id": 5, "content": "预习下周内容", "priority": "高", "done": False, "deadline": "2024-01-13"},
]
print("=" * 40)
print("📋 我的待办清单")
print("=" * 40)
# 1. 统计:已完成/未完成
total = len(tasks)
done_count = len(list(filter(lambda t: t["done"], tasks)))
print(f"总任务: {total} | 已完成: {done_count} | 待完成: {total - done_count}")
# 2. 筛选:只看未完成的任务
undone = list(filter(lambda t: not t["done"], tasks))
print(f"\n--- 待完成任务 ({len(undone)}项) ---")
# 3. 按优先级排序(高>中>低)
priority_order = {"高": 0, "中": 1, "低": 2}
sorted_undone = sorted(undone, key=lambda t: priority_order[t["priority"]])
# 4. 格式化输出
def format_task(task):
status = "✅" if task["done"] else "⬜"
return f"{status} [{task['priority']}] {task['content']} (截止:{task['deadline']})"
formatted = list(map(format_task, sorted_undone))
for task_str in formatted:
print(task_str)
# 5. 检查是否有过期任务
today = "2024-01-15"
overdue = list(filter(lambda t: t["deadline"] < today and not t["done"], tasks))
if overdue:
print(f"\n⚠️ 警告!{len(overdue)}个任务已过期: {[t['content'] for t in overdue]}")
预期输出:
========================================
📋 我的待办清单
========================================
总任务: 5 | 已完成: 1 | 待完成: 4
--- 待完成任务 (4项) ---
⬜ [高] 完成数学作业 (截止:2024-01-15)
⬜ [高] 预习下周内容 (截止:2024-01-13)
⬜ [中] 整理房间 (截止:2024-01-16)
⬜ [低] 背英语单词 (截止:2024-01-20)
⚠️ 警告!1个任务已过期: ['预习下周内容']
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:map/filter 返回的是迭代器,不是列表
# ❌ 错误:直接打印 map 对象
result = map(lambda x: x * 2, [1, 2, 3])
print(result) # <map object at 0x7f...> 打印的是内存地址!
# ✅ 正确:转成列表
result = list(map(lambda x: x * 2, [1, 2, 3]))
print(result) # [2, 4, 6]
坑 2:lambda 里的变量作用域
# ❌ 错误:lambda 捕获的是变量,不是值
funcs = [lambda x: x + i for i in range(3)]
print([f(1) for f in funcs]) # [3, 3, 3] 全部是3!
# ✅ 正确:显式传值
funcs = [lambda x, i=i: x + i for i in range(3)]
print([f(1) for f in funcs]) # [1, 2, 3]
坑 3:reduce 需要导入,但经常忘记
# ❌ 错误:直接用 reduce
total = reduce(lambda a, b: a + b, [1, 2, 3]) # NameError!
# ✅ 正确:先导入
from functools import reduce
total = reduce(lambda a, b: a + b, [1, 2, 3], 0) # 记得给初始值!
坑 4:sorted 不会修改原列表
# ❌ 错误:以为 sorted 会原地修改
nums = [3, 1, 2]
sorted(nums)
print(nums) # [3, 1, 2] 原数组没变!
# ✅ 正确:sorted 返回新列表,或者用 .sort()
nums = [3, 1, 2]
nums.sort()
print(nums) # [1, 2, 3]
坑 5:filter 后的空列表判断
# ❌ 错误:直接用 if filter 结果
result = filter(lambda x: x > 100, [1, 2, 3])
if result: # 迭代器永远为真!
print("有结果")
# ✅ 正确:转成列表再判断
result = list(filter(lambda x: x > 100, [1, 2, 3]))
if result:
print("有结果") # 这才不打印
性能小贴士:列表推导式比 map+lambda 更快
# 大数据量时,列表推导式性能更好
import time
data = range(1000000)
start = time.time()
result = [x * 2 for x in data]
t1 = time.time() - start
start = time.time()
result = list(map(lambda x: x * 2, data))
t2 = time.time() - start
print(f"列表推导式: {t1:.3f}s, map: {t2:.3f}s")
# 典型结果:列表推导式更快
调试技巧:用 partial 生成带参数的函数
from functools import partial
# 调试时想给函数预设参数?
def debug_print(msg, item):
print(f"[DEBUG] {msg}: {item}")
return item
# 生成一个固定 msg 的版本
my_debug = partial(debug_print, "处理元素")
# 用在 map 里
result = list(map(my_debug, [1, 2, 3]))
✏️ 练习题
练习 1(2 分钟):基础抄改
- 输入:numbers = [10, 20, 30, 40, 50]
- 预期输出:[20, 40, 60, 80, 100](每个数翻倍)
- 提示:用 map + 列表推导式两种方式
练习 2(2 分钟):基础 if 判断
- 输入:ages = [12, 18, 25, 17, 30]
- 预期输出:['未成年', '成年', '成年', '未成年', '成年']
- 提示:在 map 里的函数里加 if/else
练习 3(3 分钟):新数据处理
- 输入:处理 ["apple", "banana", "cherry", "date"],输出每个单词的长度
- 预期输出:[5, 6, 6, 4]
- 提示:用 map(len, 数组)
练习 4(3 分钟):串个项目 2 和 3
- 用项目 2 的总分计算 + 项目 3 的排序逻辑,给这个列表排名:
people = [
{"name": "A", "score": 85},
{"name": "B", "score": 92},
{"name": "C", "score": 78},
]
- 预期输出:排名 B>A>C
- 提示:
sorted(..., key=lambda x: x["score"], reverse=True)
练习 5(挑战题,5 分钟):报错分析
- 以下代码运行后报 TypeError: 'NoneType' object is not iterable,找出问题并修复:
def filter_even(numbers):
return list(filter(lambda x: x % 2 == 0, numbers))
result = filter_even([1, 2, 3, 4, 5])
print(filter_even(None)) # 这里报错
- 提示:参数校验可以加
if not numbers: return []
作业:做一个「数据清洗脚本」
- 需求:处理一批用户数据,去重、筛选、统计
- 功能点:
1. 从字典列表读取用户数据(含姓名、年龄、城市、注册时间)
2. 筛选出年龄 18-30 岁的用户
3. 统计每个城市的用户数量
4. 按年龄降序输出用户列表 - 加分项:
1. 支持从 JSON 文件读取
2. 输出格式化表格 - 验收标准:能跑起来 + 有注释 + 输出符合预期
- 提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学了 3 个核心点:
- map/filter/reduce 三剑客能替代 80% 的 for 循环
- 匿名函数 lambda 让高阶函数用起来更简洁
- sorted/enumerate/zip 是处理列表的瑞士军刀
延伸学习:
- 官方文档:Python 内置函数
- 《Python编程:从入门到实践》第 5 章(列表进阶)
- 视频:B站「小甲鱼」Python 教程第 30-35 讲
互动钩子:你在处理什么数据时最想用这些数组函数?学生成绩、电商订单、还是爬虫结果?评论区聊聊,说说你的场景,老粉优先回复!

评论(0)