「python|语言特性」为什么python的推导式语法一用就上瘾?

Table of Contents

引子:写法对比

1""" 2以下代码的功能是获取给定的数组中所有的偶数 3""" 4all_numbers = [1, 2, 3, 4, 5, 6, 7, 8] 5# for循环写法 6even_numbers = [] 7for number in all_numbers: 8 if number % 2 == 0: 9 even_numbers.append(number) 10 11# 列表推导式写法 12even_numbers = [number for number in all_numbers if number % 2 == 0]

上面两种的写法实现的功能是一致的。这意味着我们使用推导式的写法并不是出于功能需求,而是出于非功能需求,比如:代码更加易读、代码更加整洁等等

为什么使用推导式

  • 由于for循环的意思是重复执行某些操作,代表的是"How",如果我们使用的是for循环的写法来获取所有的偶数,那么对于阅读代码的人来说,需要查看for循环中到底进行了哪些操作,才能够得出结论:这段代码取了给定的所有整数,并将偶数放到了新列表中
  • 而推导式的写法因为可以抽象为变量名 = 推导式,而赋值语句的意思是某个变量的值等于某个表达式/某个内容,代表的是"What",所以如果我们使用的是推导式的写法,则对于阅读代码的人来说,这段代码的意思就是生成了某个内容,这个内容是给定所有整数中的所有偶数
  • 也就是说,推导式的使用有助于代码阅读者更快更准确地知道代码片段的意图。

什么时候使用推导式

像上述引子中的情况一样,如果我们想要从当前的数据中重新生成满足某些条件的数据时,我们可以选择使用推导式。常见的比如:获取所有的奇数/偶数,生成所有数值的平方/某个计算结果,数据类型转换(列表转换成字典、字符串反序列化成python对象)等等

可以总结为两个要素:

  • 我们代码段的意图是得到某个数据(列表/元组/集合/字典)
  • 过程中如果涉及的条件判断或者计算或者函数调用需要简单

如果涉及的处理逻辑比较复杂,需要将处理逻辑独立成一个函数,然后在推导式中调用。比如调用标准库json.loads来反序列化数据:

1records: List[Dict] = [json.loads(json_content) for json_content in input_data]

明白了使用推导式的好处以及使用场景后,我们来介绍各个python原生容器类型的推导式如何写。 而介绍之后我们可以意识到各个容器类型的推导式语法基本一致,从中也可以看到python语法的简洁性和一致性。

列表推导式

列表的语法为: [process_logic(element) for element in container if conditon_is_met(element)] 列表推导式就是将for循环的循环体用中括号包裹起来,然后将for条件放在列表末尾,如下:

1"""生成元素相同的新列表""" 2>>> [n for n in [1, 2, 3]] 3[1, 2, 3] 4 5"""生成所有值是原值两倍的列表""" 6>>> [n * 2 for n in [1, 2, 3]] 7[2, 4, 6] 8 9"""生成所有值是原值平方的列表""" 10>>> [n**2 for n in range(10)] 11[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 12 13"""获取原列表中所有的偶数""" 14>>> [n for n in [1, 2, 3] if n % 2] 15[1, 3]

元组推导式

将列表推导式的中括号改为小括号即可变成元组推导式。 但是,(expression code)这个语法糖已经先被迭代器占用了,所以生成的其实是元组推导式对应的迭代器而不是元组。 如果要获得元组,需要用tuple()方法进行转换:

1"""查看小括号语法糖生成的对象类型""" 2>>> print(type((number for number in range(10)))) 3<class 'generator'> 4>>> print((number for number in range(10))) 5<generator object <genexpr> at 0x00857F08> 6 7"""转换成元组""" 8>>> print(tuple((number for number in range(10)))) 9(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 10 11"""tuple()包括后, 内层的括号可以省略""" 12>>> print(tuple(number for number in range(10))) 13(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 14 15"""获取所有的偶数""" 16>>> print(tuple(number for number in range(10) if number % 2 == 0)) 17(0, 2, 4, 6, 8)

集合推导式

集合推导式的写法,就是将列表推导式的中括号改为花括号即可,语法如下: {process_logic(element) for element in container if conditon_is_met(element)} 例子如下:

1"""生成所有元素的集合""" 2>>> {s for s in [1, 2, 1, 0]} 3set([0, 1, 2]) 4 5"""生成所有元素平方的集合""" 6>>> {s**2 for s in [1, 2, 1, 0]} 7set([0, 1, 4]) 8>>> {s**2 for s in range(10)} 9set([0, 1, 4, 9, 16, 25, 36, 49, 64, 81]) 10 11"""生成所有奇数元素的集合""" 12>>> {s for s in [1, 2, 3] if s % 2} 13set([1, 3])

字典推导式

字典推导式和集合推导式相似,只是元素需要写成键值对(键: 值)的形式,如下: {key_logic(element): value_logic for element in container if conditon_is_met(element)} 例子如下:

1"""从元素为二元组的列表生成字典""" 2>>> {k: v for k, v in [(1, 2), (3, 4)]} 3{1: 2, 3: 4} 4 5"""从元素为二元组的元组生成字典""" 6>>> {k: v for k, v in (('I', 1), ('II', 2))} 7{'I': 1, 'II': 2} 8 9"""从数值迭代器生成键等于值的字典""" 10>>> {n: n for n in range(2)} 11{0: 0, 1: 1} 12 13"""生成键为ascii码字符, 值为对应ascii码值的字典""" 14>>> {chr(n): n for n in (65, 66, 66)} 15{'A': 65, 'B': 66} 16 17 18"""将元素为二元组的元组中满足条件的所有元素转换成字典""" 19>>> {k: v for k, v in (('a', 0), ('b', 1)) if v == 1} 20{'b': 1}

多重推导式/嵌套推导式

其实就是多重循环如何改写成推导式的问题。写法也很简单,就是将多重循环按照外层到内层的顺序拼成一行,反正推导式内,如下: [process(element_1, element_2) for element_1 in items_1 for element_2 in items_2] 等于以下多重循环的写法:

1results = [] 2for element_1 in items_1: 3 for element_2 in items_2: 4 results.append(process(element_1, element_2))

例子如下:

1""" 2生成字符在前, 数值在后的组合 3由于对字符的遍历在前, 所有字符相同的元素会聚在一起 4""" 5 6>>> [f"{char}{number}" for char in "ABCD" for number in range(4)] 7['A0', 'A1', 'A2', 'A3', 'B0', 'B1', 'B2', 'B3', 'C0', 'C1', 'C2', 'C3', 'D0', 'D1', 'D2', 'D3'] 8 9""" 10生成字符在前, 数值在后的组合 11由于对数值的遍历在前, 所有数值相同的元素会聚在一起 12""" 13>>> [f"{char}{number}" for number in range(4) for char in "ABCD"] 14['A0', 'B0', 'C0', 'D0', 'A1', 'B1', 'C1', 'D1', 'A2', 'B2', 'C2', 'D2', 'A3', 'B3', 'C3', 'D3'] 15 16 17"""生成二元组集合""" 18>>> {(m, n) for n in range(2) for m in range(3, 5)} 19set([(3, 0), (3, 1), (4, 0), (4, 1)])

三重循环的推导式会写了吗?

1>>> [f"{first}{second}{third}" for first in "AB" for second in range(2) for third in "XY"] 2['A0X', 'A0Y', 'A1X', 'A1Y', 'B0X', 'B0Y', 'B1X', 'B1Y']

学会了吗?(●ˇ∀ˇ●)

好书推荐:

好课推荐:

写文不易,如果对你有帮助的话,来一波点赞、收藏、关注吧~👇

Mastodon