Python学习笔记16:生成器

Python学习笔记16:生成器

在前文Python学习笔记15:推导式的结尾我们已经引出了Python中的另一项特性:生成器。

要说明的是生成器和推导式在写法上是极为相似的,除了生成器是用()来包裹以外。但实际上,他们有很大不同。

基本概念

我们现在看一个简单的例子:

import requests
urls = ("https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448",
        "https://www.runoob.com/w3cnote/python-spider-intro.html",
        "https://www.runoob.com/w3cnote/secure-wordpress-nginx.html",
        "https://cn.python-requests.org/zh_CN/latest/")
resps = []
for url in urls:
    resp = requests.get(url)
    resps.append(resp)
for resp in resps:
    print(len(resp.text))

在这个例子中,我们读取一个url列表,然后用requests模块获取其内容,然后输出内容的长度。

我们现在改写为推导式:

import requests
urls = ("https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448",
        "https://www.runoob.com/w3cnote/python-spider-intro.html",
        "https://www.runoob.com/w3cnote/secure-wordpress-nginx.html",
        "https://cn.python-requests.org/zh_CN/latest/")
resps = [requests.get(url) for url in urls]
for resp in resps:
    print(len(resp.text))

如果你注意输出的话,就不难发现,程序需要等待一会,然后才会一股脑输出所有结果。

这也不难理解,因为输出是要等到推导式处理完所有url后才会开始,那如果我们把推导式换成生成器呢?

import requests
urls = ("https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448",
        "https://www.runoob.com/w3cnote/python-spider-intro.html",
        "https://www.runoob.com/w3cnote/secure-wordpress-nginx.html",
        "https://cn.python-requests.org/zh_CN/latest/")
for resp in (requests.get(url) for url in urls):
    print(len(resp.text))

注意,生成器必须放在一个循环语句中,所以这里并没有采用复制给resps变量的做法。

我们可以注意到,此时结果输出是依次进行,中间会有或长或短的间隔,这是因为生成器的特性与推导式大为不同。

优缺点

如果你接触过安卓开发,可能会知道安卓开发中的gallery组件,也就是我们日常使用手机中很常见的预览图片时候左右滑动的那个,这个组件的实现其实和Python中的生成器异曲同工。

要知道,在很多时候我们的程序依然要考虑硬件性能,比如加载巨量数据或者图片等很占用资源的内容。而安卓平台的gallery组件就是如此,因为图片加载是很浪费内存的,你不可能把一组图片全部都加载入内存,一来浪费内存,二来也没有必要,毕竟用户一次只能看到一张图片。所以安卓给gallery加入了这样的特性:一次只会把当前图片和前后两张图片加载入内存,这样用户在左右滑动的时候几乎不会感觉到加载延迟,但又极大节省了不必要的资源消耗。

而Python的生成器和gallery的理念完全一致,可以把它看成一个Python版的gallery,当我们用循环或别的方式获取这个生成器的某一个元素时,生成器才会实时生成这个元素,当你不再访问这个元素的时候,它会立即抛弃。

所以在上边这个例子中,我们是一边获取url一边读取网络内容,然后进入以此往复。

我们可以看到生成器具有以下优点:

  • 节约系统资源。
  • 可以将集中性的耗时操作切分为小段,避免长时间没有响应。

但显然也不是没有缺点的,如果我们需要一次性获取多个元素并依照这多个元素来进行处理,就显得很不方便了。

生成器函数

除了直接把生成器作为表达式使用,我们还可以将其包装成函数。

import requests
urls = ("https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448",
        "https://www.runoob.com/w3cnote/python-spider-intro.html",
        "https://www.runoob.com/w3cnote/secure-wordpress-nginx.html",
        "https://cn.python-requests.org/zh_CN/latest/")


def getUrlRes(urls: tuple):
    for url in urls:
        yield requests.get(url)

for resp in getUrlRes(urls):
    print(len(resp.text))

在上边这个例子中,我们把生成器包装成了一个函数getUrlRes,这很有用,我们屏蔽了生成器的实现细节,其它人只要使用就可以了。

应该注意到,我们在生成器里返回值的时候使用的是yield而非return。这不难理解,如果使用return,程序会立即返回,getUrlRes也不会再次执行,而yield将告诉解释器,这里是一个生成器,在外部程序获取一个值的同时,生成器函数会挂起,直到外部程序进入下一次循环/遍历,此时生成器会从挂起的地方再次运行,并返回下一个值。

好了,关于生成器的话题就到这里了,接下来的一段时间我会探究一下Python中面向对象的部分,比照其它语言,挖掘一下前边没有深入的部分。在这部分梳理完毕后,我会重新阅读一遍《Head First 设计模式》,并用Python和类图的形式整理一遍设计模式的内容。

谢谢阅读。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值