Python入门系列34 - 推导式与生成器

小黄用python

共 3232字,需浏览 7分钟

 ·

2020-01-27 23:20

Python入门系列34

f5e2b202c03457f8597f6001cf34f090.webp

推导式与生成器


本篇阅读时间约为 6 分钟。


1

前言


从本篇开始,进入 Python 的技巧篇,介绍下编程时比较 pythonic 的写法,有些写法会非常简洁,比如本文要介绍的推导式。


推导式在各大教程中最常见的是列表推导式,但实际上不仅仅是列表可以进行推导,集合、字典都有着自己相应的推导式。当然,像廖雪峰老师写的教程中,对应的叫法是列表生成式,下面让我们来一一看下。


2

案例需求


老规矩,依然先给出一个案例的场景,通过此场景来介绍代码的编写与实现思路。


案例场景:


现有一个 list ,其中包含着一堆数字,比如 1- 10。若需要将其中的每个数字都进行平方操作,最终放回 list。此段程序如何编写呢?


简单思考一下,看过入门系列之前案例的童鞋们,在没学习列表推导式之前。肯定能想到两种方法。


1. 用 for 循环遍历每个元素节点,在 for 循环中进行平方操作,最后在放回到原有 list 中。


2. 使用 Python 内置函数 map 来实现,第一个参数传入一段计算平方的逻辑函数,第二个参数传入原有包含 1-10 的 list 即可。


1 和 2 的实现思路有了,代码就不在书写了,忘记的同学可以去翻看下高阶函数的那篇文章。


下面介绍下,如何使用列表推导式来完成上述需求。


3

列表推导式


代码实现如下:



# 定义一个包含 1-10 的 list
list_a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 实现列表表达式,用新的 list 接收下,看的明确一些
list_b = [i*i for i in list_a]
print(list_b)


d7577d59d81070216b441ac81c8035b6.webp


实际上,说起列表推导式,此处介绍的也只能是 Python 实现的语法而已,Python 解释器看到此写法的语法,会将其进行相应的语法解析。解释一下,上述代码中的列表推导式写法。


既然叫列表推导式,所以 list_b 的定义使用定义 list 的 [] 进行定义。在 [] 中的内容,可以使用表达式来书写,依然使用 for 进行对原列表遍历,与普通的 for 循环不同的是,简单计算逻辑,可以直接使用遍历出来的变量放在 for 关键词的前面进行逻辑处理。


[i*i for i in list_a]    # 列表推导式


如果说列表推导式中书写的是简单的表达式,那么自然也可以进行一些逻辑判断。


假设只想让原列表中的偶数进行平方计算的逻辑,可以修改代码如下:


# 定义一个包含 1-10 的 list
list_a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 实现列表表达式,用新的 list 接收下,看的明确一些
list_b = [i * i for i in list_a if i % 2 == 0]
print(list_b)


b758d2d26a2d916364a12536b4456100.webp


语法上来讲只需继续在 for 循环后面加上判断逻辑即可。


4

集合推导式


集合推导式,与列表推导式类似,既然叫集合推导式,那么最终要的结果必然是以集合形式的语法写出来的,所以只需要将我们上面推导式地方的代码,由 list 改为 set 即可。


# 定义一个包含 1-10 的 list
list_a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 实现集合表达式,用新的变量接收下,看的明确一些
b = {i * i for i in list_a if i % 2 == 0}
print(type(b))
print(b)


37c6e92ee943320020d2ef9597170ed4.webp


集合推导式,修改了定义集合语法的位置,使用 {} 定义,打印结果变成了 set 。类型也是 set。


5

字典推导式


字典推导式与前面介绍的两种有些区别,因为字典是两个值,key 与 value,所以在写法上,不能仅仅改变定义,还需借助字典本身的方法才能实现字典推导式,先看错误的写法,如下:


302595d72e41389e69bdda14634805c5.webp

执行一下,提示错误,解包的时候有太多值了。


正确的写法:


# 定义一个字典变量gok(Glory of Kings,王者荣耀的意思)
dict_gok = {
'name': '小妲己',
'age': '18',
'gender': '女'
}

dict_b = {key: vaule for key, vaule in dict_gok.items()}
print(dict_b)


446811b78fa1786b0c1e2537d2fc6c8e.webp


正确的写法是字典调用 items方法,将 key - value 取出使用。


小技巧,通过取出时交换 key - value 位置,得到的字典值也相反:


6a2ba683fdf7e82feef17328bb253b24.webp


6

生成器generator


你一定会奇怪,为什么没有元组推导式?那么我们来试试,若按照上面的逻辑来一次元组推导式的写法会怎么样呢?


# 定义一个包含 1-10 的 list
list_a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 实现集合表达式,用新的变量接收下,看的明确一些
b = (i * i for i in list_a if i % 2 == 0)
print(type(b))
print(b)


627d7b4ceee6c5a916debea935ab89b8.webp


可以发现,使用“元组定义推导式,生成出来的是一个 generator 的类型。对于 generator 的使用方法有两种,一种是直接调用 next 方法,还有一种通过 for 循环遍历使用,推荐使用 for 循环。


4954c4931f567a9f134428b5539c7b39.webp


此种方式调用非得累死,generator 保存的是算法,每次调用 next ,就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,还会主动抛出 StopIteration 的错误。


9798b8969a6f48b66a0bbc36026d2d34.webp


所以说,还是 for 循环最省事:


9e5acbfb5e46efed8695a430611fd1d7.webp


7

生成器的优点


通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

廖雪峰的官方网站 - 生成器


在廖雪峰老师官方网站(自行百度查看即可) - 生成器的定义处,其实就很清楚的说明了生成器与列表推导式最大的区别,这里笔者直接引用过来了,而生成器最大的优点就是节省内存,不会因为数据庞大而导致内存溢出。


当然说到生成器,并不仅仅有上面的写法可以写出生成器。在函数中,通过 yield 关键字来代替 return 关键词,也可以生成对应的 generator 生成器。


def test():
for i in range(5):
print('before yield i')
yield i
print('after yield i')

a = test()
print(type(a))
for i in a:
print(i)


e1b6ac22f70694a321e48bdb731822b4.webp


与return不同,虽然 yield 会将当前值先返回调用处,但依然会执行 yield 后的逻辑,可以看到上面的案例中,返回了 i 后,依然打印了“after yield i”。


8

总结


列表生成器在编写代码时,用得好可以大大简洁代码,而生成器有助于大数据遍历时的操作。


笔者早期刚写代码时,就遇到了大数据读取到内存的问题,那时也是初学 Python ,对于生成器的了解还不是那么深刻,所以没有利用上,依然清晰地记着项目第一版的代码,就是暴力的将 3~4 GB 文件内容直接读到内存中,最后程序跑着跑着就内存溢出了,直接导致程序自己死掉了。


如果当时利用好生成器,想必后来也不用那么麻烦的使用“分而治之”的思想去处理文件了。所以当遇到大数据时,不妨利用生成器试试,尤其是 yield 关键词,使用得当会省去不少麻烦呢!


至此完!


23b84aeae4ebda1f496e6acee77e94c6.webp


浏览 53
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报