Python基础指南之列表推导式的入门与灵活运用
一、开篇:让循环消失的魔法
前面几篇文章我们学完了列表的所有基础操作。今天我们要学的是Python中最有"Python味"的特性之一——列表推导式(List Comprehension)。
如果你问我,什么代码一看就是"Python新手写的"?我的回答往往是:用五行的 for 循环创建列表,而明明可以一行推导式搞定。列表推导式不仅是语法糖——它更短、更快、更易读,是Pythonic代码的标志之一。
很多初学者觉得推导式"有点难读",于是回避不用。但一旦你掌握了它,你会发现它不仅不难读,反而比等价的for循环更清晰地表达了意图——“我在创建一个新列表,规则是xxx”。今天这篇文章,我会带你从最简单的推导式入门,一路到嵌套、条件、多循环的高级应用。
二、列表推导式的基本语法
2.1 从for循环到推导式
# 传统for循环——创建一个平方数列表
squares = []
for x in range(10):
squares.append(x ** 2)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 列表推导式——同样的事情,一行搞定
squares = [x ** 2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 语法结构:[表达式 for 变量 in 可迭代对象]
# 1. 表达式:对每个元素做什么操作
# 2. for 变量 in 可迭代对象:遍历的数据源
2.2 最简单的推导式
# 收集原始数据(不做变换) nums = [x for x in range(10)] print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 等同于 list(range(10)),但展示了推导式的基本形式 # 对每个元素应用函数 words = ['hello', 'world', 'python'] upper_words = [w.upper() for w in words] print(upper_words) # ['HELLO', 'WORLD', 'PYTHON'] # 对每个元素做运算 prices = [100, 200, 300, 400] with_tax = [p * 1.1 for p in prices] print(with_tax) # [110.0, 220.0, 330.0, 440.0] # 从字符串中提取 text = 'Python编程' chars = [c for c in text] print(chars) # ['P', 'y', 't', 'h', 'o', 'n', '编', '程'] # 调用函数 import math roots = [math.sqrt(x) for x in range(1, 6)] print(roots) # [1.0, 1.414..., 1.732..., 2.0, 2.236...] # 保留两位小数 roots = [round(math.sqrt(x), 2) for x in range(1, 6)] print(roots) # [1.0, 1.41, 1.73, 2.0, 2.24]
三、带条件的推导式
3.1 过滤条件(if在for后面)
# 只保留偶数 numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens = [x for x in numbers if x % 2 == 0] print(evens) # [2, 4, 6, 8, 10] # 只保留长度大于3的单词 words = ['a', 'ab', 'abc', 'abcd', 'abcde'] long_words = [w for w in words if len(w) > 3] print(long_words) # ['abcd', 'abcde'] # 多个if条件——相当于and # 保留能被2整除且能被3整除的数(即能被6整除) result = [x for x in range(50) if x % 2 == 0 if x % 3 == 0] print(result) # [0, 6, 12, 18, 24, 30, 36, 42, 48] # 等价于 result = [x for x in range(50) if x % 2 == 0 and x % 3 == 0] # 过滤出非空非空白字符串 data = ['hello', '', ' ', 'world', '\t', 'python'] valid = [s for s in data if s.strip()] print(valid) # ['hello', 'world', 'python']
3.2 条件表达式(if-else在for前面)
# if-else在for前面:对每个元素做"转换"(二选一)
# 不能被过滤掉,长度不变!
# 偶数为本身,奇数为0
nums = [1, 2, 3, 4, 5]
result = [x if x % 2 == 0 else 0 for x in nums]
print(result) # [0, 2, 0, 4, 0]
# 大于等于60为'及格',否则为'不及格'
scores = [85, 45, 92, 58, 78, 30]
grades = ['及格' if s >= 60 else '不及格' for s in scores]
print(grades) # ['及格', '不及格', '及格', '不及格', '及格', '不及格']
# 混合使用:过滤 + 转换
# 只保留及格的分数,并标注等级
scores = [85, 45, 92, 58, 78, 30]
levels = [
'优秀' if s >= 90 else '良好' if s >= 80 else '中等' if s >= 70 else '及格'
for s in scores if s >= 60
]
print(levels) # ['良好', '优秀', '中等']
# 关键区分:
# [表达式 for x in lst if 条件] → if在for后面=过滤,元素数量可能变少
# [A if 条件 else B for x in lst] → if-else在for前面=转换,元素数量不变
3.3 过滤+转换的组合
# 完整形式:[转换表达式 for x in iterable if 过滤条件]
# 场景:获取所有及格分数的平方根
scores = [85, 45, 92, 58, 78, 30, 66]
import math
passing_roots = [round(math.sqrt(s), 2) for s in scores if s >= 60]
print(passing_roots) # [9.22, 9.59, 8.83, 8.12]
# 场景:获取文件列表中所有.txt文件的大写文件名
files = ['readme.txt', 'main.py', 'config.txt', 'test.py', 'data.csv']
txt_upper = [f.upper() for f in files if f.endswith('.txt')]
print(txt_upper) # ['README.TXT', 'CONFIG.TXT']
# 场景:找出100以内所有素数
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
primes = [x for x in range(2, 100) if is_prime(x)]
print(primes)
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
四、嵌套循环的推导式
4.1 双层循环
# 传统for循环——笛卡尔积
pairs = []
for x in range(3):
for y in range(2):
pairs.append((x, y))
print(pairs)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
# 推导式——顺序和for循环一致
pairs = [(x, y) for x in range(3) for y in range(2)]
print(pairs)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
# 核心规则:推导式中for的顺序 = 嵌套for循环的顺序
# 外层for在前,内层for在后
# 组合颜色和尺寸
colors = ['红色', '蓝色']
sizes = ['S', 'M', 'L']
products = [(c, s) for c in colors for s in sizes]
print(products)
# [('红色', 'S'), ('红色', 'M'), ('红色', 'L'),
# ('蓝色', 'S'), ('蓝色', 'M'), ('蓝色', 'L')]
# 配合条件过滤
# 排除同色同尺寸(当颜色=红色且尺寸=L时排除)
filtered = [(c, s) for c in colors for s in sizes
if not (c == '红色' and s == 'L')]
print(filtered)
# [('红色', 'S'), ('红色', 'M'), ('蓝色', 'S'), ('蓝色', 'M'), ('蓝色', 'L')]
4.2 扁平化嵌套列表
# 二维列表→一维列表
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# 传统方式
flat = []
for row in matrix:
for item in row:
flat.append(item)
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 推导式
flat = [item for row in matrix for item in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 只保留偶数
flat_evens = [item for row in matrix for item in row if item % 2 == 0]
print(flat_evens) # [2, 4, 6, 8]
# 三维→一维
cube = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]],
]
flat_cube = [item for plane in cube for row in plane for item in row]
print(flat_cube) # [1, 2, 3, 4, 5, 6, 7, 8]
4.3 矩阵操作
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# 转置矩阵
transposed = [[row[i] for row in matrix] for i in range(3)]
print(transposed) # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# 等价于 zip(*matrix)
print([list(col) for col in zip(*matrix)]) # [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# 生成乘法表
mult_table = [[i * j for j in range(1, 10)] for i in range(1, 10)]
for row in mult_table:
print(' '.join(f'{n:2d}' for n in row))
# 生成单位矩阵
n = 5
identity = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
for row in identity:
print(row)
# [1, 0, 0, 0, 0]
# [0, 1, 0, 0, 0]
# ...
五、推导式中的表达式技巧
5.1 复杂表达式
# 表达式可以是任意合法的Python表达式
# 调用方法
words = ['hello', 'world', 'python']
caps = [w.capitalize() for w in words]
print(caps) # ['Hello', 'World', 'Python']
# 字符串格式化
names = ['小明', '小红', '小刚']
greetings = [f'你好,{name}!' for name in names]
print(greetings) # ['你好,小明!', '你好,小红!', '你好,小刚!']
# 三元表达式
nums = [1, 2, 3, 4, 5]
labels = ['奇数' if x % 2 else '偶数' for x in nums]
print(labels) # ['奇数', '偶数', '奇数', '偶数', '奇数']
# 元组构造
data = [1, 2, 3, 4, 5]
indexed = [(i, x) for i, x in enumerate(data)]
print(indexed) # [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
# 多重赋值的使用
pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
swapped = [(b, a) for a, b in pairs]
print(swapped) # [('a', 1), ('b', 2), ('c', 3)]
# zip的组合
names = ['小明', '小红', '小刚']
ages = [25, 23, 26]
profiles = [f'{n}今年{a}岁' for n, a in zip(names, ages)]
print(profiles) # ['小明今年25岁', '小红今年23岁', '小刚今年26岁']
5.2 调用函数
# 表达式可以调用任何函数
import hashlib
passwords = ['hello', 'world', 'python123']
hashed = [hashlib.sha256(p.encode()).hexdigest()[:8] for p in passwords]
print(hashed)
# 调用自定义函数
def grade(score):
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
scores = [95, 82, 73, 45, 88, 91]
grades = [grade(s) for s in scores]
print(grades) # ['A', 'B', 'C', 'F', 'B', 'A']
# 使用内置函数
names = [' Alice ', 'BOB ', ' Charlie']
cleaned = [n.strip().capitalize() for n in names]
print(cleaned) # ['Alice', 'Bob', 'Charlie']
5.3 列表推导式中的海象运算符(Python 3.8+)
# 使用 := 避免重复计算
import math
# 传统推导式——计算两次
# roots = [math.sqrt(x) for x in range(10) if math.sqrt(x) > 2]
# 海象运算符——只计算一次
roots = [s for x in range(10) if (s := math.sqrt(x)) > 2]
print(roots) # [2.236..., 2.449..., 2.645..., 2.828..., 3.0]
# 更实际的例子——处理代价高昂的计算
def expensive_computation(x):
"""模拟代价高昂的计算"""
import time
time.sleep(0.01)
return x ** 2
# 传统方式:计算两次
# result = [expensive_computation(x) for x in range(10)
# if expensive_computation(x) > 50]
# 海象运算符:只计算一次
result = [val for x in range(10)
if (val := expensive_computation(x)) > 50]
print(result) # [64, 81]
# 在条件中使用多次
data = ['hello', 'world', 'hi', 'python', 'hey']
# 找出包含'o'且长度大于4的词
long_with_o = [w for w in data if 'o' in w and len(w) > 4]
print(long_with_o) # ['hello', 'world', 'python']
六、推导式的性能
6.1 推导式为什么更快
import time
n = 1000000
# 方式一:传统for循环
start = time.perf_counter()
result1 = []
for i in range(n):
result1.append(i ** 2)
t1 = time.perf_counter() - start
# 方式二:列表推导式
start = time.perf_counter()
result2 = [i ** 2 for i in range(n)]
t2 = time.perf_counter() - start
# 方式三:map + lambda
start = time.perf_counter()
result3 = list(map(lambda i: i ** 2, range(n)))
t3 = time.perf_counter() - start
print(f'for循环: {t1:.3f}秒')
print(f'推导式: {t2:.3f}秒')
print(f'map + lambda: {t3:.3f}秒')
print(f'推导式比for循环快 {(t1 / t2 - 1) * 100:.0f}%')
# 推导式快在:
# 1. 循环在C层面执行,不经过Python的迭代器协议
# 2. append方法不需要每次在Python层面查找
# 3. 结果列表的内存可以预估,减少扩容次数
6.2 推导式 vs 生成器表达式
import sys
n = 1000000
# 列表推导式——一次性创建整个列表(占用内存)
list_comp = [i ** 2 for i in range(n)]
print(f'列表推导式内存: {sys.getsizeof(list_comp):,} 字节')
# 大约 8MB
# 生成器表达式——惰性求值(不占内存)
gen_expr = (i ** 2 for i in range(n)) # 注意:圆括号不是元组
print(f'生成器表达式内存: {sys.getsizeof(gen_expr)} 字节')
# 大约 200 字节
# 生成器表达式适合:
# 1. 只需要遍历一次
# 2. 数据量非常大,不想创建中间列表
# 3. 作为函数参数传递
# 例子:
total = sum(i ** 2 for i in range(10000000)) # 不创建中间列表
# 而不是:
# total = sum([i ** 2 for i in range(10000000)]) # 先创建1000万的列表!
七、推导式的最佳实践与常见误区
7.1 什么时候不该用推导式
# 误区一:推导式太长太复杂——应该拆开写
# 不推荐:一个推导式做太多事情
# result = [process(x) for y in data if condition(y) for x in y.get_items() if x.status == 'active']
# 这种代码需要读者在脑子里编译好几次
# 推荐:拆分为多步
active_items = []
for y in data:
if condition(y):
for x in y.get_items():
if x.status == 'active':
active_items.append(process(x))
# 误区二:推导式有副作用
# 不推荐:在推导式中做有副作用的事(打印、修改外部变量等)
# result = [print(x) or x for x in data] # 不要这样写!
# 推导式应该只用于"创建新列表",不应该有副作用
# 误区三:为了用推导式而用推导式
# 如果for循环更清晰,就用for循环
# 代码清晰度 > 炫技
# 好的推导式:
active_users = [u for u in users if u.is_active] # 简单清晰
# 可能需要拆开的:
# result = [
# format_item(x) if condition(x) else default_value(x)
# for group in data
# for x in group if x is not None
# ] # 这个就有点长了
7.2 可读性检查清单
# 检查你的推导式是否可读:
# 1. 能一句话描述它的作用吗?
# ✅ [x for x in data if x > 0] → "取出所有正数"
# ❌ 过于复杂的嵌套
# 2. 所有变量名都清晰吗?
# ✅ [user.name for user in active_users]
# ❌ [x.n for x in u if x.a]
# 3. 可以一眼看到"表达式"和"数据源"吗?
# ✅ [f(x) for x in data]
# ❌ 表达式和数据源混在一起
# 4. 有超过两个for吗?
# 如果是——考虑拆开或用传统循环
# 5. 有超过两个if吗?
# 如果是——考虑用辅助函数
# 良好实践的示例:
# 简单过滤
evens = [x for x in numbers if x % 2 == 0]
# 简单映射(数据转换)
names = [user.name for user in users]
# 过滤+映射
active_names = [user.name for user in users if user.is_active]
# 嵌套(确保可读)
flat_cells = [cell for row in table for cell in row]
# 构建字典(字典推导式)
name_map = {user.id: user.name for user in users}
# 构建集合(集合推导式)
unique_tags = {tag for article in articles for tag in article.tags}
八、推导式家族:不只是列表
8.1 字典推导式
# {key_expr: value_expr for item in iterable if condition}
# 基本用法
squares = {x: x ** 2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 反转字典的键值
original = {'a': 1, 'b': 2, 'c': 3}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict) # {1: 'a', 2: 'b', 3: 'c'}
# 过滤
scores = {'小明': 85, '小红': 92, '小刚': 78, '小李': 95}
passing = {name: score for name, score in scores.items() if score >= 90}
print(passing) # {'小红': 92, '小李': 95}
# 从两个列表构建字典
keys = ['name', 'age', 'city']
values = ['小明', 25, '北京']
person = {k: v for k, v in zip(keys, values)}
print(person) # {'name': '小明', 'age': 25, 'city': '北京'}
8.2 集合推导式
# {expr for item in iterable if condition}
# 基本用法
squares = {x ** 2 for x in range(10)}
print(squares) # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
# 去重
words = ['hello', 'world', 'hello', 'python', 'world']
unique_words = {w.lower() for w in words}
print(unique_words) # {'hello', 'world', 'python'}
# 提取所有标签
articles = [
{'title': '文章1', 'tags': ['Python', '编程']},
{'title': '文章2', 'tags': ['Java', '编程']},
{'title': '文章3', 'tags': ['Python', 'Web']},
]
all_tags = {tag for article in articles for tag in article['tags']}
print(all_tags) # {'Python', '编程', 'Java', 'Web'}
九、实战:推导式解决实际问题
9.1 数据清洗流水线
def clean_dataset(raw_data):
"""用推导式流水线清洗数据集"""
# 过滤掉None和空记录
valid = [r for r in raw_data if r is not None and r]
# 格式化每个字段
cleaned = [
{
'name': r['name'].strip().title(),
'email': r['email'].strip().lower(),
'age': int(r['age']),
'score': float(r['score']),
}
for r in valid
if r.get('name') and r.get('email') and r.get('age')
]
# 只保留有效分数的记录
cleaned = [r for r in cleaned if 0 <= r['score'] <= 100]
return cleaned
raw = [
{'name': ' alice ', 'email': 'ALICE@test.com', 'age': '25', 'score': '85.5'},
None,
{},
{'name': 'BOB', 'email': 'bob@test.com', 'age': '30', 'score': '92.0'},
{'name': '', 'email': '', 'age': '0', 'score': '0'},
{'name': ' charlie ', 'email': 'CHARLIE@test.com', 'age': '28', 'score': '105'},
]
cleaned = clean_dataset(raw)
for r in cleaned:
print(r)
9.2 构建复杂数据结构
# 生成日历数据
import calendar
import datetime
def month_calendar(year, month):
"""生成一个月的日历数据(二维列表)"""
cal = calendar.monthcalendar(year, month)
# 标注周末
annotated = [
[
{
'day': day,
'is_weekend': i >= 5, # 第6、7列是周末
'is_today': day == datetime.date.today().day
and month == datetime.date.today().month
and year == datetime.date.today().year,
}
if day != 0 else None
for i, day in enumerate(row)
]
for row in cal
]
return annotated
# 生成嵌套菜单
menu_items = ['文件', '编辑', '视图', '帮助']
sub_items = {
'文件': ['新建', '打开', '保存', '退出'],
'编辑': ['撤销', '重做', '剪切', '复制', '粘贴'],
'视图': ['放大', '缩小', '全屏'],
'帮助': ['关于', '更新'],
}
menu = [
{
'name': item,
'submenu': [
{'name': sub, 'shortcut': f'Ctrl+{chr(65 + j)}'}
for j, sub in enumerate(sub_items.get(item, []))
]
}
for i, item in enumerate(menu_items)
]
for m in menu:
print(f'{m["name"]}: {[s["name"] for s in m["submenu"]]}')
十、本篇小结
列表推导式是Python最具代表性的特性之一:
- 基本语法:
[表达式 for 变量 in 可迭代对象 if 条件] - 过滤 vs 转换:
if在for后是过滤(减少元素),if-else在for前是转换(元素数量不变) - 嵌套循环:for的顺序和嵌套for循环一致,外层在前内层在后
- 性能:推导式比等价for循环快(C层面执行),但大量数据考虑用生成器表达式
- 可读性第一:太复杂的推导式拆开写成for循环更好
- 推导式家族:列表
[x for x in ...]、字典{k:v for k,v in ...}、集合{x for x in ...}、生成器(x for x in ...)
列表推导式是Python编程的"标配技能"。当你开始习惯用推导式替代简单的for循环后,你会发现代码变得更短、更清晰、更Pythonic。下一篇我们将进入列表嵌套与二维列表的操作技巧——矩阵运算、棋盘游戏、电子表格等场景的核心数据结构。
以上就是Python基础指南之列表推导式的入门与灵活运用的详细内容,更多关于Python列表推导式的资料请关注脚本之家其它相关文章!
相关文章
TensorFlow——Checkpoint为模型添加检查点的实例
今天小编就为大家分享一篇TensorFlow——Checkpoint为模型添加检查点的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-01-01
Python使用pickle模块报错EOFError Ran out of input的解决方法
这篇文章主要介绍了Python使用pickle模块报错EOFError Ran out of input的解决方法,涉及Python异常捕获操作处理相关使用技巧,需要的朋友可以参考下2018-08-08


最新评论