前言

迭代器和生成器以及上下文管理器都是python的高级特性,它们的区别在于实例化出来的对象中是否含有一些函数。在for循环中不管是传入的可迭代对象还是迭代器对象(生成器对象)
都会先进行调用iter()方法将其变为迭代器,再进行执行next()方法,直到抛出异常

生成器

1.生成器

生成器(generator)也是一种迭代器,在每次迭代时返回一个值,直到抛出 StopIteration 异常。它有两种构造方式:

(1).生成器表达式

和列表推导式的定义类似,生成器表达式使用 () 而不是 [] ,比如:
在这里插入图片描述
可以看出生成器表达式无法像列表推导式那样直接输出,它和可迭代对象一样只能采用for循环调用next()函数,原因在于range返回的是一个可迭代对象,列表推导式之所以能直接print就是因为[]将可迭代对象转为列表。

(2).生成器函数

含有 yield 关键字的函数,调用该函数时会返回一个生成器。
在这里插入图片描述
可以看到,上面的函数没有使用 return 语句返回值,而是使用了 yield 返回一个值。一个带有 yield 的函数就是一个生成器函数,当我们使用 yield 时,它帮我们自动创建了__iter__() 和 next() 方法,而且在没有数据时,也会抛出 StopIteration 异常,也就是我们不费吹灰之力就获得了一个迭代器,非常简洁和高效。
带有 yield 的函数执行过程比较特别:

  1. 调用该函数的时候不会立即执行代码,而是返回了一个生成器对象;
  2. 当使用 next() (在 for 循环中会自动调用 next() ) 作用于返回的生成器对象时,函数 开始执行,在遇到 yield 的时候会『暂停』,并返回当前的迭代值;
  3. 当再次使用 next() 的时候,函数会从原来『暂停』的地方继续执行,直到遇到 yield语 句,如果没有 yield 语句,则抛出异常;
    整个过程看起来就是不断地 执行->中断->执行->中断 的过程。一开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象;然后,当我们使用 next() 作用于它的时候,它开始执行,遇到 yield 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的变量值,也就是执行时的上下文环境被保留起来;当再次使用 next() 的时候,从原来中断的地方继续执行,直至遇到 yield ,如果没有 yield ,则抛出异常。简而言之,就是 next 使函数执行, yield 使函数暂停。

我们也可以写一个生成器函数或类来实现range函数相同功能

生成器类的写法
在这里插入图片描述
生成器函数的写法
在这里插入图片描述

可以看到,使用生成器的方法非常简洁,不用自定义 iter() 和 next() 方法。另外,在处理大文件的时候,我们可能无法一次性将其载入内存,这时可以通过构造固定长度的缓冲区,来不断读取文件内容。有了 yield ,我们就不用自己实现读文件的迭代器了。

2.进阶使用

我们除了能对生成器进行迭代使它返回值外,还能:

  1. 使用 send() 方法给它发送消息;
  2. 使用 throw() 方法给它发送异常;
  3. 使用 close() 方法关闭生成器;

(1).send()方法

在这里插入图片描述

在上面的代码中,我们先调用 next() 方法,使函数开始执行,代码执行到 yield 1 的时候暂停,返回了 1;接着,我们执行了 send() 方法,它会恢复生成器的运行,并将发送的值赋给上次中断时 yield 表达式的执行结果,也就是 value1,这时我们又执行了send()方法控制台打印出 value1 的值,并继续执行,直到遇到 yield 后暂停,此时返回 2;类似地,再次执行 send() 方法,将值赋给value2。
简单地说, send() 方法就是 next() 的功能,加上传值给 yield 。

(2).throw()方法

在这里插入图片描述
可以看到, throw() 方法向生成器函数传递了 ValueError 异常,此时代码进入 except
ValueError 语句,遇到 yield ‘Error’,暂停并返回 Error 字符串。

简单的说, throw() 就是 next() 的功能,加上传异常给 yield 。

(3).close() 方法

我们可以使用 close() 方法来关闭一个生成器。生成器被关闭后,再次调用 next() 方法,不管能否遇到 yield 关键字,都会抛出 StopIteration 异常,
在这里插入图片描述

3.小结

  1. yield 把函数变成了一个生成器。
  2. 生成器函数的执行过程看起来就是不断地 执行->中断->执行->中断 的过程。
  3. 一开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象;
  4. 然后,当我们使用 next() 作用于它的时候,它开始执行,遇到 yield 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的数 据,也就是执行时的上下文环境被保留起来;
  5. 当再次使用 next() 的时候,从原来中断的地方继续执行,直至遇到 yield ,如果没有 yield ,则抛出异常。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐