Python(二十五) 列表、字典、集合推导式详解推导式是 Python 中一种非常常用的语法。
它可以用简洁的代码,根据已有数据快速生成新的数据。
通俗地说:
推导式就是把 for 循环生成数据的过程,压缩成一行来写。例如,我们想生成一个列表,里面保存 1 到 5 的平方。
普通写法:
输出:
使用列表推导式:
nums = [x * x for x inrange(1, 6)]输出:
两段代码结果一样。
推导式的好处是:
Python 中常见的推导式主要有三种:
| | |
|---|
| [表达式 for 变量 in 可迭代对象] | |
| {键表达式: 值表达式 for 变量 in 可迭代对象} | |
| {表达式 for 变量 in 可迭代对象} | |
另外还有一种很像推导式的写法,叫生成器表达式:
它不是元组推导式,而是生成器表达式。
本篇重点讲:
可迭代对象简单理解就是:可以被 for 循环遍历的数据。
常见可迭代对象:
例如:
列表可以被 for 循环遍历,所以列表是可迭代对象。
再比如:
字符串也可以被遍历,所以字符串也是可迭代对象。
表达式就是可以计算出结果的代码。
例如:
这些都可以作为推导式中的表达式。
推导式一般可以按下面的顺序理解:
列表推导式用于快速创建列表。
例如:
nums = [x for x inrange(1, 6)]输出:
这段代码可以拆成普通循环:
nums = [x for x inrange(10)]输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]生成 1 到 5 的平方:
squares = [x * x for x inrange(1, 6)]输出:
普通循环写法:
把所有单词转换成大写:
words = ["python", "java", "c++"]result = [word.upper() for word in words]输出:
['PYTHON', 'JAVA', 'C++']去掉字符串两边的空格:
names = [" 张三 ", " 李四", "王五 "]result = [name.strip() for name in names]输出:
语法:
[表达式 for 变量 in 可迭代对象 if 条件]筛选偶数:
nums = [1, 2, 3, 4, 5, 6]evens = [x for x in nums if x % 2 == 0]输出:
普通循环写法:
nums = [1, 2, 3, 4, 5, 6]words = ["hi", "python", "java", "ok"]result = [word for word in words iflen(word) > 3]输出:
筛选大于 10 且是偶数的数字:
nums = [5, 8, 12, 15, 20, 23]result = [x for x in nums if x > 10if x % 2 == 0]输出:
也可以写成:
nums = [5, 8, 12, 15, 20, 23]result = [x for x in nums if x > 10and x % 2 == 0]输出:
如果需要根据条件给出不同结果,可以使用 if...else。
语法:
[值1if 条件 else 值2for 变量 in 可迭代对象]例如,把偶数标记为 "偶数",奇数标记为 "奇数":
result = ["偶数"if x % 2 == 0else"奇数"for x in nums]输出:
['奇数', '偶数', '奇数', '偶数', '奇数']再比如,及格显示 "通过",不及格显示 "不通过":
scores = [90, 58, 76, 45]result = ["通过"if score >= 60else"不通过"for score in scores]输出:
['通过', '不通过', '通过', '不通过']这两个写法很容易混淆。
[x for x in nums if x > 0]意思是:
[x if x > 0else0for x in nums]意思是:
示例:
result1 = [x for x in nums if x > 0]result2 = [x if x > 0else0for x in nums]输出:
区别总结:
| | |
|---|
[x for x in nums if 条件] | | |
[值1 if 条件 else 值2 for x in nums] | | |
列表推导式中可以写多个 for。
例如,生成两个列表元素的所有组合:
result = [(color, size) for color in colors for size in sizes]输出:
[('红', 'S'), ('红', 'M'), ('红', 'L'), ('蓝', 'S'), ('蓝', 'M'), ('蓝', 'L')]等价于:
result.append((color, size))推导式中的 for 顺序和普通循环顺序一致。
[(color, size) for color in colors for size in sizes]对应:
把二维列表变成一维列表:
result = [num for row in matrix for num in row]输出:
[1, 2, 3, 4, 5, 6, 7, 8, 9]等价于:
生成所有乘积大于 10 的组合:
result = [(x, y) for x in a for y in b if x * y > 10]输出:
[(3, 4), (3, 5), (4, 3), (4, 4), (4, 5)]enumerate() 可以同时得到下标和元素。
names = ["张三", "李四", "王五"]result = [f"{index}: {name}"for index, name inenumerate(names)]输出:
['0: 张三', '1: 李四', '2: 王五']如果希望编号从 1 开始:
names = ["张三", "李四", "王五"]result = [f"{index}: {name}"for index, name inenumerate(names, start=1)]输出:
['1: 张三', '2: 李四', '3: 王五']zip() 可以把多个可迭代对象中对应位置的元素组合起来。
names = ["张三", "李四", "王五"]result = [f"{name}: {score}"for name, score inzip(names, scores)]输出:
['张三: 90', '李四: 85', '王五: 78']字典推导式用于快速创建字典。
{键表达式: 值表达式 for 变量 in 可迭代对象}例如,创建数字和平方的对应关系:
squares = {x: x * x for x inrange(1, 6)}输出:
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}普通循环写法:
names = ["张三", "李四", "王五"]result = {name: score for name, score inzip(names, scores)}输出:
{'张三': 90, '李四': 85, '王五': 78}这里 zip(names, scores) 会依次产生:
如果要遍历字典的键和值,一般使用 .items()。
scores = {"张三": 90, "李四": 85, "王五": 78}result = {name: score + 5for name, score in scores.items()}输出:
{'张三': 95, '李四': 90, '王五': 83}筛选成绩大于等于 80 的学生:
scores = {"张三": 90, "李四": 58, "王五": 78, "赵六": 88}passed = {name: score for name, score in scores.items() if score >= 80}输出:
普通循环写法:
scores = {"张三": 90, "李四": 58, "王五": 78, "赵六": 88}for name, score in scores.items():给所有学生成绩加 10 分:
scores = {"张三": 70, "李四": 80, "王五": 90}new_scores = {name: score + 10for name, score in scores.items()}输出:
{'张三': 80, '李四': 90, '王五': 100}把所有姓名前面加上 "学生-":
scores = {"张三": 90, "李四": 85}result = {f"学生-{name}": score for name, score in scores.items()}输出:
{'学生-张三': 90, '学生-李四': 85}scores = {"张三": 90, "李四": 85, "王五": 78}result = {score: name for name, score in scores.items()}输出:
{90: '张三', 85: '李四', 78: '王五'}注意:如果原字典中的值有重复,互换后会丢失数据。
例如:
scores = {"张三": 90, "李四": 90, "王五": 78}result = {score: name for name, score in scores.items()}可能输出:
因为字典的键不能重复。
当两个学生的成绩都是 90,互换后键都是 90,后面的值会覆盖前面的值。
5.8 使用 enumerate() 创建编号字典names = ["张三", "李四", "王五"]result = {index: name for index, name inenumerate(names, start=1)}输出:
{1: '张三', 2: '李四', 3: '王五'}根据成绩生成是否通过:
scores = {"张三": 90, "李四": 58, "王五": 76}result = {name: "通过"if score >= 60else"不通过"for name, score in scores.items()}输出:
{'张三': '通过', '李四': '不通过', '王五': '通过'}字典的键必须是不可变、可哈希的数据。
可以作为键的常见类型:
不能作为键的常见类型:
错误示例:
result = {item: len(item) for item in data} # 错误因为 item 是列表,列表不能作为字典的键。
可以改成元组:
result = {tuple(item): len(item) for item in data}输出:
集合推导式用于快速创建集合。
集合的特点是:
例如,生成平方集合:
squares = {x * x for x inrange(1, 6)}输出可能是:
普通循环写法:
集合推导式会自动去掉重复元素。
nums = [1, 2, 2, 3, 3, 3]result = {x for x in nums}输出:
这和 set(nums) 的效果类似。
nums = [1, 2, 2, 3, 3, 3]输出:
筛选偶数,并自动去重:
nums = [1, 2, 2, 3, 4, 4, 5, 6]evens = {x for x in nums if x % 2 == 0}输出:
chars = {ch for ch in text}输出可能是:
因为集合无序,所以输出顺序不固定。
words = ["Python", "python", "JAVA", "java"]result = {word.lower() for word in words}输出:
把偶数保留,奇数改成 0,再放入集合:
result = {x if x % 2 == 0else0for x in nums}输出:
注意:多个奇数都会变成 0,集合会自动去重,所以只保留一个 0。
集合推导式:
结果:
字典推导式:
{x: x * x for x inrange(3)}结果:
区别在于:
空集合必须写成:
不能写成:
因为 {} 表示空字典。
输出:
假设有一个列表:
result = [x * x for x in nums]输出:
result = {x: x * x for x in nums}输出:
{1: 1, 2: 4, 3: 9, 4: 16}result = {x * x for x in nums}输出:
输出顺序可能不同,因为集合无序。
注意:
字典在 Python 3.7 及以后会保留插入顺序。很多初学者看到推导式会不知道从哪里开始看。
建议从 for 开始读。
例如:
result = [x * 2for x inrange(1, 4)]阅读顺序:
for x in range(1, 4) 先依次取出 1、2、3所以结果是:
再看带条件的:
result = [x * 2for x inrange(1, 6) if x % 2 == 0]阅读顺序:
结果:
推导式中 if 的位置非常重要。
result = [x for x inrange(10) if x % 2 == 0]意思是:
result = ["偶数"if x % 2 == 0else"奇数"for x inrange(5)]意思是:
错误写法:
result = [x for x inrange(5) if x % 2 == 0else0]这是错误的。
如果有 else,应该写在 for 前面的表达式中。
正确写法:
result = [x if x % 2 == 0else0for x inrange(5)]推导式不是必须使用的。
它适合处理简单、清晰的数据转换。
适合:
result = [x * 2for x in nums]适合:
scores = {"张三": 90, "李四": 58}result = {name: score for name, score in scores.items() if score >= 60}适合:
words = ["Python", "python", "Java"]result = {word.lower() for word in words}这些代码逻辑都比较短,推导式很清楚。
如果逻辑很复杂,不推荐强行使用推导式。
例如:
"优秀"if score >= 90else"良好"if score >= 80else"及格"if score >= 60else"不及格"虽然这段代码可以运行,但可读性不太好。
更推荐写成普通循环:
圆括号写法不是元组推导式。
result = (x * x for x inrange(5))输出类似:
<generator object <genexpr> at 0x...>这叫生成器表达式。
生成器不会一次性生成所有数据,而是用到一个生成一个。
如果想用类似推导式的方式创建元组,要写:
result = tuple(x * x for x inrange(5))输出:
不能认为下面是元组推导式:
result = (x * x for x inrange(5))它是生成器表达式。
在 Python 3 中,推导式内部的变量不会影响外部同名变量。
result = [x for x inrange(3)]输出:
外面的 x 仍然是 100。
例如:
result = [score + 5for score in scores]比下面更清楚:
result = [x + 5for x in scores]推导式不是越短越好。
如果一行写得太复杂,学生和同事都很难读懂。
不推荐:
result = [x * y for x inrange(10) for y inrange(10) if x % 2 == 0if y % 3 == 0]如果逻辑复杂,可以写成普通循环。
推导式主要用于生成新数据。
不推荐用它来做打印、写文件、修改外部变量等操作。
不推荐:
[print(x) for x inrange(5)]更推荐:
因为打印的重点是执行动作,不是生成列表。
data = ["apple", "ant", "banana", "book"]result = {word[0]: word for word in data}可能输出:
{'a': 'ant', 'b': 'book'}原因是:
字典的键不能重复。
result = {x for x in nums}输出:
如果你需要保留重复元素,就不要用集合推导式。
应该使用列表推导式:
result = [x for x in nums]输出:
result = {x for x in [3, 1, 2]}输出顺序不一定是你写入的顺序。
如果需要有顺序的结果,请使用列表推导式。
这是空字典,不是空集合。
空集合要写:
不推荐:
scores = {"张三": 90, "李四": 58}这样可能导致运行错误。
如果要筛选字典,推荐创建一个新字典:
scores = {"张三": 90, "李四": 58}new_scores = {name: score for name, score in scores.items() if score >= 60}输出:
列表推导式会创建新列表。
result = [x * 2for x in nums]输出:
原列表 nums 没有被修改。
字典推导式和集合推导式也一样,通常是创建新对象。
列表推导式会一次性生成完整列表。
result = [x for x inrange(10000000)]如果数据量非常大,可能占用较多内存。
这种情况下可以考虑生成器表达式:
result = (x for x inrange(10000000))生成器不会一次性创建所有数据。
初学阶段先掌握列表、字典、集合推导式即可。
需求:
代码:
names = [" 张三 ", "", " 李四", "王五 ", " "]result = [name.strip() for name in names if name.strip()]输出:
解释:
scores = [95, 82, 60, 45, 78]result = ["优秀"if score >= 90else"合格"if score >= 60else"不合格"for score in scores]输出:
['优秀', '合格', '合格', '不合格', '合格']这段代码可以运行。
不过如果分类更多,建议用普通循环或函数来提高可读性。
names = ["张三", "李四", "王五"]score_dict = {name: score for name, score inzip(names, scores)}输出:
{'张三': 90, '李四': 85, '王五': 78}scores = {"张三": 90, "李四": 58, "王五": 76, "赵六": 45}passed = {name: score for name, score in scores.items() if score >= 60}输出:
tags = ["Python", "python", "Java", "java", "PYTHON"]result = {tag.lower() for tag in tags}输出:
words = ["python", "java", "c"]result = {word: len(word) for word in words}输出:
{'python': 6, 'java': 4, 'c': 1}evens = [num for row in matrix for num in row if num % 2 == 0]输出:
chars = {ch for ch in text if ch != " "}输出可能是:
{'e', 'h', 'l', 'o', 'p', 'y', 't', 'n'}错误理解:
result = {x * x for x inrange(5)}这不是字典,而是集合。
如果想生成字典,要写成:
result = {x: x * x for x inrange(5)}错误写法:
result = {x, x * x for x inrange(5)}正确写法:
result = {x: x * x for x inrange(5)}字典推导式必须有 key: value。
不推荐:
[print(x) for x inrange(3)]推荐:
错误写法:
result = [x for x inrange(5) if x % 2 == 0else0]正确写法:
result = [x if x % 2 == 0else0for x inrange(5)]result = {x for x in [3, 1, 2]}集合不保证按列表原来的顺序输出。
如果需要顺序,用列表推导式。
result = (x for x inrange(5))这不是元组,而是生成器。
如果要元组:
result = tuple(x for x inrange(5))result = {word[0]: word for word in words}输出:
apple 被覆盖了。
如果希望一个键对应多个值,可以使用普通循环配合列表。
words = ["apple", "ant", "banana", "book"] result[first].append(word)输出:
{'a': ['apple', 'ant'], 'b': ['banana', 'book']}请使用列表推导式生成 1 到 10 的平方列表。
参考答案:
result = [x * x for x inrange(1, 11)]有如下列表:
nums = [1, 2, 3, 4, 5, 6, 7, 8]请使用列表推导式筛选出所有偶数。
参考答案:
nums = [1, 2, 3, 4, 5, 6, 7, 8]evens = [x for x in nums if x % 2 == 0]有如下列表:
words = ["python", "java", "c++"]请使用列表推导式把所有字符串转换成大写。
参考答案:
words = ["python", "java", "c++"]result = [word.upper() for word in words]请使用字典推导式生成 1 到 5 的数字和平方对应关系。
参考答案:
result = {x: x * x for x inrange(1, 6)}有如下字典:
scores = {"张三": 90, "李四": 58, "王五": 76}请使用字典推导式筛选出成绩大于等于 60 的学生。
参考答案:
scores = {"张三": 90, "李四": 58, "王五": 76}passed = {name: score for name, score in scores.items() if score >= 60}有如下列表:
names = ["张三", "李四", "王五"]请生成这样的字典:
{1: "张三", 2: "李四", 3: "王五"}参考答案:
names = ["张三", "李四", "王五"]result = {index: name for index, name inenumerate(names, start=1)}有如下列表:
nums = [1, 2, 2, 3, 3, 3, 4]请使用集合推导式去重。
参考答案:
nums = [1, 2, 2, 3, 3, 3, 4]result = {x for x in nums}有如下字符串:
请使用集合推导式提取其中不同的字符。
参考答案:
chars = {ch for ch in text}有如下二维列表:
matrix = [[1, 2], [3, 4], [5, 6]]请使用列表推导式把它变成:
参考答案:
matrix = [[1, 2], [3, 4], [5, 6]]result = [num for row in matrix for num in row]有如下成绩列表:
scores = [90, 58, 76, 45]请使用列表推导式生成:
["通过", "不通过", "通过", "不通过"]参考答案:
scores = [90, 58, 76, 45]result = ["通过"if score >= 60else"不通过"for score in scores]推导式是 Python 中非常实用的语法。
可以用一句话总结:
三种常见推导式:
[表达式 for 变量 in 可迭代对象] # 列表推导式{键表达式: 值表达式 for 变量 in 可迭代对象} # 字典推导式{表达式 for 变量 in 可迭代对象} # 集合推导式带条件筛选:
[表达式 for 变量 in 可迭代对象 if 条件]带 if...else 处理:
[值1if 条件 else 值2for 变量 in 可迭代对象]记忆口诀:
最后记住一个原则: