Python列表推导式:一行代码搞定复杂循环
一、导语
刚学Python时,你是不是也写过这样的代码——先创建一个空列表,然后写一个for循环,再一行一行往里面append数据?这段代码通常要占四五行,每次写都觉得很啰嗦。其实Python早就为我们准备了一个优雅的替代方案:列表推导式(List Comprehension)。
列表推导式是Python最受欢迎的特性之一。它可以把多行的循环+条件逻辑压缩成一行,代码不仅更短,读起来也更接近"人类语言"。本文带你从入门到进阶,彻底掌握这个利器。
二、基础语法:从循环到推导式
先看一个最简单的场景:生成1到10的平方数列表。
传统写法(3行):
squares=[]foriinrange(1,11):squares.append(i**2)print(squares)# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]列表推导式(1行):
squares=[i**2foriinrange(1,11)]print(squares)# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]语法拆解一下:[表达式 for 变量 in 可迭代对象]。
for i in range(1, 11)— 和普通循环的for一样,依次取出每个元素i ** 2— 对每个元素执行的表达式,结果直接放入新列表- 外面用
[]包起来,表示我们要的是一个列表
把这个结构记牢,后面所有变体都是在这个基础上的扩展。
三、加上条件过滤:if的妙用
现实场景中,我们经常需要筛选数据。比如从一堆数字里挑出偶数再求平方。
传统写法:
even_squares=[]foriinrange(1,21):ifi%2==0:even_squares.append(i**2)列表推导式:
even_squares=[i**2foriinrange(1,21)ifi%2==0]print(even_squares)# [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]语法变为:[表达式 for 变量 in 可迭代对象 if 条件]。
注意if写在for的后面,而不是表达式前面。这恰好符合我们阅读的顺序——“从1到20里取出i,如果i是偶数,就计算i的平方”。
四、if-else:条件表达式放前面
如果筛选还不够,需要对不同情况做不同处理,就用if-else。但要记住一个重要区别:if-else写在表达式位置,if(无else)写在循环后面。
# 偶数求平方,奇数保持不变result=[i**2ifi%2==0elseiforiinrange(1,11)]print(result)# [1, 4, 3, 16, 5, 36, 7, 64, 9, 100]结构是:[结果A if 条件 else 结果B for i in ...]。这是Python的三元表达式嵌入到了推导式中。
对比记忆法:
| 场景 | 写法 | 位置 |
|---|---|---|
| 只要符合条件的 | if 条件 | for后面 |
| 符合条件的做A,否则做B | A if 条件 else B | for前面(表达式位置) |
五、嵌套循环:多维数据的处理利器
遇到二维列表(矩阵)时,列表推导式同样能大显身手。
展平二维列表:
matrix=[[1,2,3],[4,5,6],[7,8,9]]# 传统写法flat=[]forrowinmatrix:fornuminrow:flat.append(num)# 推导式:for的顺序和传统嵌套一致flat=[numforrowinmatrixfornuminrow]print(flat)# [1, 2, 3, 4, 5, 6, 7, 8, 9]注意for的书写顺序:外层循环在前,内层循环在后,这和普通嵌套循环的书写顺序完全一致,非常好记。
再加条件过滤:
# 只提取大于5的元素flat_big=[numforrowinmatrixfornuminrowifnum>5]print(flat_big)# [6, 7, 8, 9]六、实战场景:五个工作中常用的例子
6.1 清洗字符串列表
names=[" Alice ","BOB"," Charlie ","david"]clean=[name.strip().title()fornameinnames]print(clean)# ['Alice', 'Bob', 'Charlie', 'David']6.2 筛选有效邮箱
emails=["alice@qq.com","invalid","bob@163.com","not-an-email","charlie@gmail.com"]valid=[eforeinemailsif"@"ineand"."ine.split("@")[-1]]print(valid)# ['alice@qq.com', 'bob@163.com', 'charlie@gmail.com']6.3 字典列表提取字段
users=[{"name":"张三","age":28,"city":"北京"},{"name":"李四","age":35,"city":"上海"},{"name":"王五","age":22,"city":"深圳"},]names=[u["name"]foruinusers]print(names)# ['张三', '李四', '王五']# 30岁以上用户的城市cities=[u["city"]foruinusersifu["age"]>30]print(cities)# ['上海']6.4 文件路径批量处理
importos# 获取当前目录下所有.py文件的绝对路径py_files=[os.path.abspath(f)forfinos.listdir(".")iff.endswith(".py")]6.5 生成笛卡尔积
colors=["红","蓝","绿"]sizes=["S","M","L"]combinations=[f"{c}-{s}"forcincolorsforsinsizes]print(combinations)# ['红-S', '红-M', '红-L', '蓝-S', '蓝-M', '蓝-L', '绿-S', '绿-M', '绿-L']七、不止列表:字典推导式与集合推导式
学会了列表推导式,另外两种推导式就是换一对括号的事。
字典推导式:
# 把列表转成字典:名字→长度words=["python","java","golang"]word_len={w:len(w)forwinwords}print(word_len)# {'python': 6, 'java': 4, 'golang': 6}# 键值互换d={"a":1,"b":2,"c":3}reversed_d={v:kfork,vind.items()}print(reversed_d)# {1: 'a', 2: 'b', 3: 'c'}集合推导式:
# 获取字符串中所有不重复的字母text="hello world"unique_chars={cforcintextifc!=" "}print(unique_chars)# {'d', 'e', 'h', 'l', 'o', 'r', 'w'}八、注意事项:推导式不是万能药
列表推导式虽好,但过度使用会让代码变得难以阅读:
# 反面教材:嵌套三层还带条件,读起来很痛苦result=[x*yforxinrange(10)ifx%2==0foryinrange(10)ify%3==0]使用原则:
- 两层嵌套是上限:超过两层,老老实实用传统
for循环 - 单个推导式不超过一行:如果一行写不完(超过约80字符),拆成多行或用普通循环
- 复杂逻辑拆出来:如果表达式本身就很复杂,先定义成函数,再在推导式中调用
# 拆成函数,推导式就清晰了defnormalize(s):returns.strip().lower().replace("-","_")fields=[" User-Name ","Email-Address "," Phone-Number"]result=[normalize(f)forfinfields]print(result)# ['user_name', 'email_address', 'phone_number']九、总结
列表推导式是Python中"少即是多"哲学的典型体现。核心要点回顾:
| 要素 | 说明 |
|---|---|
| 基本语法 | [表达式 for 变量 in 可迭代对象] |
| 条件筛选 | [表达式 for 变量 in 可迭代对象 if 条件] |
| 条件分支 | [A if 条件 else B for 变量 in 可迭代对象] |
| 嵌套循环 | [表达式 for 外层 in 容器1 for 内层 in 容器2] |
| 字典推导 | {键: 值 for ... in ...} |
| 集合推导 | {元素 for ... in ...} |
掌握了列表推导式,你会发现很多原本需要五六行的循环逻辑,一行就能优雅地解决。代码更简洁,也更Pythonic。下次写循环前,不妨先想想:能不能用推导式搞定?