Python: 列表进阶与元组
上一课学了列表的基本操作,这一课来点更"Python 风格"的东西——列表推导式写一行顶五行的快感、元组这个"不可变的列表"什么时候用。学完这些,你写的代码会越来越像真正的 Python 程序员。
1. 列表排序进阶
第 10 课学了 sort() 和 sorted() 的基本用法,但它们还可以接受一个 key 参数来指定"按什么规则排序"。
PYTHON
# 按字符串长度排序
words = ["Python", "Java", "JavaScript", "C", "Go"]
sorted_by_len = sorted(words, key=len)
print(sorted_by_len) # ['C', 'Go', 'Java', 'Python', 'JavaScript']
# 按最后一个字符排序
words = ["banana", "apple", "cherry", "date"]
sorted_by_last = sorted(words, key=lambda w: w[-1])
print(sorted_by_last) # ['banana', 'date', 'apple', 'cherry']
# 按元组中某个元素排序——后面会学
scores = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
sorted_scores = sorted(scores, key=lambda s: s[1], reverse=True)
print(sorted_scores) # [('Bob', 92), ('Alice', 85), ('Charlie', 78)]
💡
key 参数超级实用—— 你可以传任何接受一个参数并返回一个值的函数,sorted() 会用这个返回值来排序。上面的 lambda 是匿名函数,后面会细讲。
▶ 示例:排行榜生成(难度⭐⭐)
PYTHON
# 游戏排行榜——按分数降序排列
players = [
("玩家1", 3500),
("玩家2", 5200),
("玩家3", 2800),
("玩家4", 4100),
]
# 按分数降序
ranked = sorted(players, key=lambda p: p[1], reverse=True)
print("=== 排行榜 ===")
for i, (name, score) in enumerate(ranked, 1):
print(f"第{i}名:{name} —— {score}分")
运行结果:
TEXT
📖 仅展示
=== 排行榜 ===
第1名:玩家2 —— 5200分
第2名:玩家4 —— 4100分
第3名:玩家1 —— 3500分
第4名:玩家3 —— 2800分
2. 列表推导式入门
列表推导式(List Comprehension)是 Python 最受欢迎的特性之一——用一行代码生成一个新列表。
(1) 基本语法
TEXT
📖 仅展示
[表达式 for 变量 in 可迭代对象]
(2) 传统写法 vs 推导式
PYTHON
# 生成 0~9 的平方——传统写法
squares = []
for i in range(10):
squares.append(i ** 2)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 用列表推导式——一行搞定
squares = [i ** 2 for i in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
(3) 带条件的推导式
PYTHON
# 筛选出偶数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [n for n in numbers if n % 2 == 0]
print(evens) # [2, 4, 6, 8, 10]
# 把字符串转成大写
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words) # ['HELLO', 'WORLD', 'PYTHON']
# 筛选长度 >= 5 的单词
words = ["cat", "elephant", "dog", "giraffe", "ant"]
long_words = [w for w in words if len(w) >= 5]
print(long_words) # ['elephant', 'giraffe']
💡 推导式虽然简洁,但不要滥用。 如果逻辑复杂(比如嵌套循环或多个条件),写成普通 for 循环更容易读懂。简洁 ≠ 可读。
▶ 示例:数据处理(难度⭐⭐)
PYTHON
# 原始数据——可能有空值和多余空白
raw_data = [" Alice ", "", " Bob ", " Charlie ", "", " 赵六 "]
# 用推导式一步完成:去空白 + 过滤空值
cleaned = [name.strip() for name in raw_data if name.strip()]
print(cleaned) # ['Alice', 'Bob', 'Charlie', '赵六']
# 数值处理
temps_c = [0, 10, 20, 30, 40]
temps_f = [c * 9 / 5 + 32 for c in temps_c]
print(temps_f) # [32.0, 50.0, 68.0, 86.0, 104.0]
输出:
TEXT
📖 仅展示
# 执行成功
3. 元组是什么
元组(tuple)用圆括号 () 定义,和列表很像,但有一个关键区别——元组不可变,创建后不能修改。
PYTHON
# 定义元组
point = (3, 5)
color = (255, 0, 0)
empty = ()
single = (42,) # 注意:单元素元组必须有逗号!
print(point) # (3, 5)
print(type(point)) # <class 'tuple'>
(1) 元组不可变
PYTHON
point = (3, 5)
# point[0] = 10 ← 报错!元组不能修改
print(point[0]) # 3——可以读取,不能修改
(2) 为什么需要元组
元组不可变的特点带来了两个好处:
1. 可以作为字典的键(列表不行,因为可变)
PYTHON
# 元组可以作为字典的键
locations = {
(35.68, 139.76): "东京",
(31.23, 121.47): "上海",
}
print(locations[(35.68, 139.76)]) # 东京
# 列表不能做键
# d = {[1, 2]: "error"} ← 会报错!
2. 表示一组不该被修改的数据
PYTHON
# 坐标、颜色、配置等"不会变"的数据适合用元组
RGB_RED = (255, 0, 0) # 红色值不应该被修改
DEFAULT_CONFIG = ("localhost", 8080, "admin")
💡 什么时候用元组、什么时候用列表? 一个简单的判断标准:数据需要修改就用列表,不需要修改就用元组。 函数返回多个值时通常用元组(下一节)。
4. 元组解包
元组一个非常实用的特性——解包(unpacking),把元组的元素直接赋值给多个变量:
PYTHON
point = (3, 5)
x, y = point # 解包
print(x) # 3
print(y) # 5
# 交换变量——本质就是元组解包
a, b = 10, 20
a, b = b, a # 右侧先构造元组 (20, 10),再解包赋值
print(a, b) # 20 10
# 函数返回多个值
def get_min_max(numbers):
return min(numbers), max(numbers)
result = get_min_max([3, 1, 4, 1, 5])
print(result) # (1, 5)
low, high = get_min_max([3, 1, 4, 1, 5])
print(f"最小值:{low},最大值:{high}") # 最小值:1,最大值:5
(1) 使用 _ 忽略不需要的值
PYTHON
data = ("Alice", 25, "北京", "工程师")
name, _, city, _ = data # 用 _ 表示"我不关心这个"
print(f"{name}住在{city}") # Alice住在北京
▶ 示例:坐标计算(难度⭐⭐)
PYTHON
# 定义两个点
p1 = (1, 2)
p2 = (4, 6)
# 解包计算距离
x1, y1 = p1
x2, y2 = p2
distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
print(f"两点距离:{distance:.2f}") # 5.00
# 用枚举遍历带索引
fruits = ["苹果", "香蕉", "橘子"]
for i, fruit in enumerate(fruits, 1):
print(f"{i}. {fruit}")
运行结果:
TEXT
📖 仅展示
两点距离:5.00
1. 苹果
2. 香蕉
3. 橘子
5. 列表与元组的选型
| 特点 | 列表 | 元组 |
|---|---|---|
| 可变 | ✅ 可增删改 | ❌ 不可变 |
| 速度 | 稍慢 | 稍快 |
| 内存 | 稍大 | 稍小 |
| 能做字典键 | ❌ | ✅ |
| 使用场景 | 需要频繁修改的数据 | 固定不变的数据 |
| 典型用途 | 购物车、待办列表、动态数据 | 坐标、颜色、函数返回值 |
💡 一个实用的习惯: 如果你不确定用哪个,先用列表。绝大多数场景下列表更灵活。等你发现某个数据确实不该被修改时,再改成元组。
6. 常见应用场景
- 排行榜排序:用
sorted(key=lambda ...)按自定义规则对复杂数据排序。 - 数据清洗:用列表推导式一行完成过滤、转换、去空白。
- 坐标与几何:用元组表示点、矩形、RGB 颜色等固定值。
- 多返回值函数:函数返回
(最小值, 最大值)元组,调用方直接解包。 - 枚举遍历:
enumerate()返回(索引, 元素)元组,用解包直接取值。
❓ 常见问题
Q 列表推导式和普通的 for 循环哪个快?
A 列表推导式通常更快,因为它在 C 层面执行循环,而不是 Python 层面。但差距只在数据量很大时(几万条以上)才明显。日常写代码,优先选可读性好的写法,不用太纠结性能。
Q 单元素元组为什么必须加逗号?
(42) 和 (42,) 有什么区别?A
(42) 中的括号被 Python 解释为数学分组,不是元组——type((42)) 结果是 <class 'int'>。(42,) 才是元组。所以单元素元组必须加逗号:(42,)。Q
sorted() 的 key 参数怎么用?能举一个实际例子吗?A 最常见的场景是按对象的某个属性排序。比如按字符串长度:
sorted(words, key=len)。按列表中元组的第二个元素:sorted(data, key=lambda x: x[1])。按字典的值(下一课):sorted(dict.items(), key=lambda x: x[1])。关键是掌握 key 接受一个"把每个元素映射成排序依据"的函数。📖 小节
sorted()的key参数可以指定排序规则,配合lambda非常灵活- 列表推导式
[表达式 for 变量 in 可迭代对象 if 条件]一行生成新列表 - 元组用
()定义,不可变,可以作为字典的键 - 单元素元组必须加逗号:
(42,),否则是整数 - 元组解包直接把多个值赋给多个变量,交换变量就是利用了这个特性
- 列表适合需要修改的数据,元组适合固定不变的数据
📝 作业
-
基础题(难度⭐):用列表推导式生成 1 到 20 之间所有能被 3 整除的数字的平方。提示:
[表达式 for n in range(...) if 条件] -
进阶题(难度⭐⭐):给定一个名字列表
names = [" alice ", "BOB", " Charlie", " david "],用列表推导式一步完成:去掉首尾空格 + 首字母大写其余小写。提示:strip()去空白,title()或capitalize()改大小写。 -
挑战题(难度⭐⭐⭐):写一个函数
analyze_numbers(*args),接收任意多个数字,返回一个元组(最大值, 最小值, 平均值, 大于平均值的个数)。提示:用*args接收可变参数,用列表推导式统计大于平均值的个数。