区别迭代器和生成器

一:迭代器

定义:

  迭代器就是实现了迭代器协议的对象。那么小伙伴们就要问了,什么是迭代器协议呢?

设计的知识点:

  迭代器协议:即对象实现了__iter__()和__next__()两个方法

  __iter__()方法:返回迭代器对象本身

  __next__()方法:返回容器的下一个元素,在结尾时引发一个StopIteration异常终止迭代器

那么迭代器究竟该如何创建呢,创建如下:

迭代器的创建:使用iter()方法返回一个迭代器对象,iter()的参数为可迭代对象。那么这里有小伙伴又要问了,什么是可迭代对象??

 可迭代对象:即实现了__iter__()方法的对象,可以使用该方法返回一个迭代器对象。

工作过程:

 一般迭代器工作过程 :

 现在我们做一个例子来更加形象的讲解迭代器的工作流程,这里我将用List对象来进行实例。

 首先我们先定义一个List对象,调用__iter__()方法,可以发现List对象确实是一个可迭代对象(即实现了__iter__()方法)。然后我们调用__next__()方法,可以发现系统提示并没有__next__()这个属性,因此表示List对象不是一个迭代器对象。

>>> list=[1,2,3,4,5]
>>> list.__iter__()

>>> list.__next__()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'list' object has no attribute '__next__'

 之后,我们将使用List对象创建一个迭代器对象,然后让迭代器对象调用__next__()方法,一次次返回可迭代对象(即List对象)的元素,直到最后抛出一个StopIteration异常。---注意,产生一个异常并不等于出现错误,而是告诉外部调用者迭代完成了,外部的调用者尝试去捕获这个异常去做进一步的处理。

>>> li=list.__iter__()
>>> li.__next__()
1
>>> li.__next__()
2
>>> li.__next__()
3
>>> li.__next__()
4
>>> li.__next__()
5
>>> li.__next__()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

这就是迭代器的整个工作过程!!

迭代器迭代可变对象:

当迭代器迭代可变对象时,一个序列的迭代器只是记录当前到达了序列中的第几个元素,所以如果在迭代过程中改变了序列的元素。更新会立即反应到所迭代的条目上。


 for循环迭代器工作过程 :

 首先,我们同样使用List对象作为例子。定义一个List对象后,使用for循环遍历List对象。

>>> list=[1,2,3,4,5]
>>> for x in list:
...     print(list[x-1])
...
1
2
3
4
5

 那么,究竟for循环是怎样遍历/迭代可迭代对象的呢?首先,List对象先使用__iter__()方法返回一个迭代器,之后使用这个跌打器一次次调用__next__()方法,一个接着一个返回List对象的元素,并将返回的元素赋值给变量x。每一次的赋值都会执行一次for循环体中的内容。直到最后List对象中没有元素,就抛出一个StopIteration异常给外部调用者处理。

 可以参考下面的代码:

>>> list=[1,2,3,4,5]
>>> li=list.__iter__()
>>> x=li.__next__()
>>> print(list[x-1])
1
>>> x=li.__next__()
>>> print(list[x-1])
2
>>> x=li.__next__()
>>> print(list[x-1])
3
>>> x=li.__next__()
>>> print(list[x-1])
4
>>> x=li.__next__()
>>> print(list[x-1])
5
>>> x=li.__next__()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

注:for循环本质上也是一种迭代器。

原理:

 迭代器工作原理:

 for循环工作原理:


二:生成器

定义:

 生成器是一个包含有yield的函数,同时也是一个迭代器。那么,小伙伴们可能要问yield是什么呢?哈哈,yield就是生成器的重点!!我会在接下来慢慢进行讲解。

设计的知识点:

 生成器实际上是特殊的一种迭代器,即完全可以像使用迭代器一样使用生成器。说了这么多,究竟该怎样创建生成器呢?

生成器的创建:

 1、使用生成器函数的返回值,返回一个生成器对象

 2、使用生成器表达式的返回值,返回一个生成器对象(生成器表达式在后面讲解)

>>> def fun(x):
...     print("前")
...     yield x
...     print("后")
...
>>> ge=fun(1)
>>> ge

>>> ge1=(x for y in [1,2,3,4,5])
>>> ge1
 at 0x000000DB1AF149E8>

从上面的代码我们可以看出,变量ge和ge1都是generator object(生成器对象)。因此,两种创建方式都可以返回一个生成器对象。

生成器表达式:类似于列表,只是将[]换成了(),生成器表达式返回一个生成器对象。

>>> list=[1,2,3,4,5]
>>> ge1=[y for y in list ]
>>> ge1
[1, 2, 3, 4, 5]
>>> type(ge1)

>>> ge1=(x for x in list)
>>> ge1
 at 0x000000DB1AF14AF0>

由以上代码可以知道,两种创建的不是一种对象。


工作过程:

  接下来,我们同样用一个例子来具体解释生成器函数的工作过程。

首先,我们先利用生成器函数创建一个生成器f,之后我们不断调用__next__()方法,直到最后抛出一个异常。我们再来观察代码的运行情况。

>>> def fun(x):
...     z=0
...     print("前")
...     yield x
...     print("中")
...     yield 2
...     y=yield z
...     print(y)
...     print("后")
...
>>> f=fun(1)
>>> next(f)
前
1
>>> next(f)
中
2
>>> next(f)
0
>>> next(f)
None
后
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

  首先我们先来看这段代码是怎样执行的:我们先定义了一个生成器函数(内部包含yield),然后我们使用该函数创建一个生成器对象 f。

  可以看出,当我们调用该函数时,该函数并没有执行(因为如果执行了函数,会输出一些结果)。当我们第一次调用next()方法的时候,发现程序居然执行了!!!为什么会这样呢?这就要从next()方法开始说起。

  next()作用:第一次调用next()方法会启动生成器函数,并执行到最靠近的yield语句的位置。

  ok,明白了next()方法的作用之后,我们就明白了为什么第一次的执行结果是:前。但是为什么还有一个 1 呢?这就要来看看yield的作用了!

  yield:在每一次执行next()方法之后,会将代码的执行权限交给生成器函数去执行,一直执行到yield语句(yield语句也会执行)。yield语句会返回一个值,并保存生成器中前面一次执行的状态,并将代码的执行权限交还给调用生成器的一方去执行调用方的代码。

  当我们完全明白了next()和yield的作用之后,就能理解上面例子的执行流程了。第二次调用next()方法时,会从上一次执行的代码行之后执行(即从语句: print("中") 开始执行),一直执行到yield语句,返回一个值:2。第三次调用netx()方法时,同理,会输出如上的结果。一次次调用next()方法,一直执行到最后没有了yield语句,就会抛出一个异常。


其实生成器除了next()方法外,还有send()、close()、throw()方法。

>>> def fun(x):
...     z=0
...     print("qian")
...     yield 1
...     print("zhong")
...     yield x
...     y=yield z
...     print(y)
...     print("hou")
...
>>> f=fun(2)
>>> next(f)
qian
1
>>> next(f)
zhong
2
>>> next(f)
0
>>> f.send(9)
9
hou
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

  send()作用:在yield z执行后,执行send()方法后悔传递一个值给变量y。若在yield z执行之前执行send()方法,则不会传递值给变量y。

>>> def fun(x):
...     z=0
...     print("qian")
...     yield 1
...     print("zhong")
...     yield x
...     y=yield z
...     print(y)
...     print("hou")
...
>>> f=fun(2)
>>> next(f)
qian
1
>>> f.close()
>>> next(f)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
 close():当调用该方法后,就会退出生成器函数,后面的调用会直接返回StopIteration异常。
>>> def gen():
...     while True:
...         try:
...             yield 'normal value'
...             yield 'normal value 2'
...             print('here')
...         except ValueError:
...             print('we got ValueError here')
...         except TypeError:
...             break
...
>>> g=gen()
>>> print(next(g))
normal value
>>> print(g.throw(ValueError))
we got ValueError here
normal value
>>> print(next(g))
normal value 2
>>> print(g.throw(TypeError))

throw():调用该方法后,会忽略try语句中的代码,跳到throw的异常处执行,并执行下一个yield语句后停止;或者在没有下一个yield的时候直接进行到程序的结尾。


在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;

如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代;

如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。


区别return和yield:

 return一旦使用,其(函数)作用域的变量等状态会消失,并且会将代码的执行权限交给外部的调用者;如果外部调用者不再调用该函数,就不会再将执行权限交还给函数;若return后再次调用该函数,原函数的作用域等状态会消失,生成的是一个新的函数的作用域(内部的各种状态也是新的)。     

yield会在外部调用者调用next()/send(None)方法时,权限跳转到生成器中,执行生成器的代码直到yield语句,yield返回一个值给外部调用者,并且将代码执行权限也交给外部调用者,此时生成器中的状态等会保存,直到下一次外部调用者调用next()方法,会将代码的执行权限还给生成器,并从上一次的状态位置开始执行。       

原理:

生成器表达式

生成器函数

三:两者的区别

区别:

迭代器(多迭代):可以多次迭代

>>> myiterator = [x*x for x in range(3)]
>>> for x in myiterator:
...     print(x)
...
0
1
4
>>> for x in myiterator:
...     print(x)
...
0
1
4

生成器(单迭代):只能迭代一次

>>> mygenerator = (x*x for x in range(3))
>>> for x in  mygenerator:
...     print(x)
...
0
1
4
>>> for x in  mygeneration:
...     print(x)
...
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'mygeneration' is not defined


好了,这篇博客讲解了迭代器和生成器的知识,关于两者的实际应用并没有讲解,楼主会在以后慢慢补上!!

你可能感兴趣的:(python)