列表推导式是Python中用于创建新列表的典型写法,核心价值是把“遍历数据源、对元素做变换、按条件筛选”压缩成一行可读表达式。它并非单纯炫技,而是在明确表达意图:我要基于某个可迭代对象生成一个新列表。本文围绕基本语法、条件过滤、嵌套循环、表达式技巧、性能差异以及字典和集合推导式展开,代码均按原文逻辑整理。
一、从for循环到列表推导式
传统写法先用空列表接收结果,再循环append;推导式把表达式和遍历写在方括号中。语法结构为:[表达式 for 变量 in 可迭代对象]。表达式决定对每个元素做什么,for部分决定数据来源。
- squares = []
- for x in range(10):
- squares.append(x ** 2)
- print(squares)
- squares = [x ** 2 for x in range(10)]
- print(squares)
复制代码
最简单的推导式可以不做变换,只收集数据;也可以调用方法、做运算或调用函数。例如把单词转大写、给价格加税、对数字开平方并保留两位小数:
- words = ['hello', 'world', 'python']
- upper_words = [w.upper() for w in words]
- print(upper_words)
- prices = [100, 200, 300, 400]
- with_tax = [p * 1.1 for p in prices]
- print(with_tax)
- import math
- roots = [round(math.sqrt(x), 2) for x in range(1, 6)]
- print(roots)
复制代码
二、条件过滤与条件表达式
if在for后面时表示过滤,元素数量可能变少。例如只保留偶数、保留长度大于3的单词,或者用多个if实现类似and的条件。
- numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- evens = [x for x in numbers if x % 2 == 0]
- print(evens)
- words = ['a', 'ab', 'abc', 'abcd', 'abcde']
- long_words = [w for w in words if len(w) > 3]
- print(long_words)
- result = [x for x in range(50) if x % 2 == 0 if x % 3 == 0]
- print(result)
- data = ['hello', '', ' ', 'world', '\t', 'python']
- valid = [s for s in data if s.strip()]
- print(valid)
复制代码
if-else写在for前面时表示转换,不是过滤,结果长度不变。例如偶数保留本身、奇数置0,或者按分数标注及格与否。
- nums = [1, 2, 3, 4, 5]
- result = [x if x % 2 == 0 else 0 for x in nums]
- print(result)
- scores = [85, 45, 92, 58, 78, 30]
- grades = ['及格' if s >= 60 else '不及格' for s in scores]
- print(grades)
复制代码
过滤和转换可以组合:[转换表达式 for x in iterable if 过滤条件]。例如只对及格分数开平方,或者从文件列表中筛出.txt并转大写。原文还给出用自定义is_prime函数配合推导式筛选100以内素数的写法。
- scores = [85, 45, 92, 58, 78, 30, 66]
- passing_roots = [round(math.sqrt(s), 2) for s in scores if s >= 60]
- print(passing_roots)
- 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)
复制代码
需要区分:if在for后面是筛选,if-else在for前面是二选一转换。混淆这两类写法,常见表现是结果元素数量与预期不一致。
三、嵌套循环、扁平化与矩阵操作
推导式中的多个for顺序与嵌套for循环一致:外层for在前,内层for在后。它可以生成笛卡尔积,也可以把二维或三维列表扁平化。
- pairs = [(x, y) for x in range(3) for y in range(2)]
- print(pairs)
- colors = ['红色', '蓝色']
- sizes = ['S', 'M', 'L']
- products = [(c, s) for c in colors for s in sizes]
- print(products)
- matrix = [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- ]
- flat = [item for row in matrix for item in row]
- print(flat)
- flat_evens = [item for row in matrix for item in row if item % 2 == 0]
- print(flat_evens)
复制代码
矩阵转置可以用嵌套推导式:外层按列索引遍历,内层按行取值。原文也指出它等价于zip(*matrix)的写法。单位矩阵则用条件表达式在行列相等时取1,否则取0。
- matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
- transposed = [[row[i] for row in matrix] for i in range(3)]
- print(transposed)
- print([list(col) for col in zip(*matrix)])
- 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)
复制代码
四、表达式技巧与海象运算符
推导式里的表达式可以是方法调用、f-string、enumerate、zip、自定义函数或hashlib等库函数。关键是让表达式保持单一职责,不要塞入过多逻辑。
- names = ['小明', '小红', '小刚']
- greetings = [f'你好,{name}!' for name in names]
- print(greetings)
- data = [1, 2, 3, 4, 5]
- indexed = [(i, x) for i, x in enumerate(data)]
- print(indexed)
- names = ['小明', '小红', '小刚']
- ages = [25, 23, 26]
- profiles = [f'{n}今年{a}岁' for n, a in zip(names, ages)]
- print(profiles)
- import hashlib
- passwords = ['hello', 'world', 'python123']
- hashed = [hashlib.sha256(p.encode()).hexdigest()[:8] for p in passwords]
- print(hashed)
复制代码
Python 3.8+支持海象运算符 :=。当过滤条件需要复用表达式结果时,可用它避免重复计算。原文用math.sqrt演示:先赋值再判断,后续直接使用该值。对于代价较高的函数,这种写法能减少一次调用。
- import math
- roots = [s for x in range(10) if (s := math.sqrt(x)) > 2]
- print(roots)
- def expensive_computation(x):
- return x ** 2
- result = [val for x in range(10) if (val := expensive_computation(x)) > 50]
- print(result)
复制代码
五、性能差异与生成器表达式
原文用time.perf_counter对比传统for循环、列表推导式和map+lambda三种写法。推导式通常更快,原因包括循环在C层面执行、append方法不必每次在Python层面查找、结果列表内存可预估从而减少扩容。
- import time
- n = 1000000
- 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
- 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}%')
复制代码
列表推导式会一次性创建完整列表,生成器表达式则惰性求值。原文用sys.getsizeof展示内存差异:百万元素的列表推导式占用较大内存,而生成器表达式对象很小。生成器表达式适合只需要遍历一次、数据量非常大、不想创建中间列表,或作为函数参数传递的场景。注意生成器表达式的圆括号不是元组。
- import sys
- n = 1000000
- list_comp = [i ** 2 for i in range(n)]
- print(f'列表推导式内存: {sys.getsizeof(list_comp):,} 字节')
- gen_expr = (i ** 2 for i in range(n))
- print(f'生成器表达式内存: {sys.getsizeof(gen_expr)} 字节')
- total = sum(i ** 2 for i in range(10000000))
复制代码
六、最佳实践与常见误区
推导式应只用于创建新列表、字典或集合,不应在其中做打印、修改外部变量等副作用操作。一个推导式如果嵌套太多for和if,或者表达式本身很长,读者需要在脑中反复编译,应该拆成多步循环或辅助函数。为了用推导式而用推导式并不可取,代码清晰度优先。
可读性检查可以从几个问题入手:能否一句话描述作用;变量名是否清晰;表达式和数据源是否一眼可见;是否超过两个for;是否超过两个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}
复制代码
七、字典推导式和集合推导式
字典推导式语法为{key_expr: value_expr for item in iterable if condition},可用于生成平方字典、反转键值、按条件过滤字典,或用zip把两个列表组装成字典。集合推导式为{expr for item in iterable if condition},天然适合去重和提取标签。
- squares = {x: x ** 2 for x in range(6)}
- print(squares)
- original = {'a': 1, 'b': 2, 'c': 3}
- reversed_dict = {v: k for k, v in original.items()}
- print(reversed_dict)
- scores = {'小明': 85, '小红': 92, '小刚': 78, '小李': 95}
- passing = {name: score for name, score in scores.items() if score >= 90}
- print(passing)
- keys = ['name', 'age', 'city']
- values = ['小明', 25, '北京']
- person = {k: v for k, v in zip(keys, values)}
- print(person)
- words = ['hello', 'world', 'hello', 'python', 'world']
- unique_words = {w.lower() for w in words}
- print(unique_words)
- 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)
复制代码
八、适用场景与落地建议
列表推导式最适合“纯变换、纯筛选、纯构建”的场景:数据预处理、字段映射、二维数据扁平化、矩阵转置、去重、标签汇总等。实战中可以把过滤和格式化组合成流水线,例如先过滤None和空记录,再对字段做strip、title等格式化,最后构造新的字典结构。原文在实战部分给出了数据清洗流水线的思路,但该段代码展示不完整,本文不补写未出现的实现,重点掌握前面的组合方式即可。需要副作用、复杂分支、异常处理或日志输出时,传统循环往往更清晰,也更容易调试。兼容性方面,海象运算符要求Python 3.8+;生成器表达式与列表推导式内存行为不同,应按数据规模和遍历次数选择。 |