迭代器和生成器

旧时模样 提交于 2019-12-02 12:06:05

迭代器(iterator)
迭代器是在python2.2中被加入的,它为类序列对象提供了一个类序列的接口。有了迭代器可以迭代一个不是序列的对象,因为他表现出了序列的行为。
什么迭代器呢?
迭代器的实质是实现了next()方法的对象,常见的元组、列表、字典都是迭代器。

迭代器中重点关注两种方法:

iter方法:返回迭代器自身。可以通过python内建函数iter()调用。

next方法:当next方法被调用的时候,迭代器会返回它的下一个值,如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。该方法可以通过 python 内建函数next()调用。

举例

内建函数iter()可以从可迭代对象中获得迭代器。

>>> it = iter([1,2,3])
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

迭代器是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()(python2中实现next())方法的对象都是迭代器。

__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常,至于它们到底是如何实现的这并不重要。

 迭代器就是实现了工厂模式的对象,它在你每次你询问要下一个值的时候给你返回。

如何创建一个迭代器

要创建一个迭代器有2种方法,其中前两种分别是:

  1. 为容器对象添加 iter() 和 next() 方法(Python 2.7 中是 next());iter() 返回迭代器对象本身 self,next() 则返回每次调用 next() 或迭代时的元素;
  2. 内置函数 iter() 将可迭代对象转化为迭代器

案例:创建迭代器容器

# Create iterator Object
class Container:
   def __init__(self, start=0, end=0):
      self.start = start
      self.end = end

   def __iter__(self):
      print("I made this iterator!")
      return self

   def __next__(self):
      print("Calling __next__ method!")
      if self.start < self.end:
         i = self.start
         self.start += 1
         return i
      else:
         raise StopIteration()


c = Container(0, 5)
for i in c:
   print(i)

 对于迭代器对象,使用for循环遍历整个数组其实是个语法糖,他的内部实现还是通过调用对象的__next__()方法。

 创建迭代器对象的好处是当序列长度很大时,可以减少内存消耗,因为每次只需要记录一个值即刻(经常看到人们介绍 Python 2.7 的 range 函数时,建议当长度太大时用 xrange 更快,在 Python 3.5 中已经去除了 xrange 只有一个类似迭代器一样的 range)。

斐波那契数列应用举例

我们如果采用正常的斐波那契数列求值过程如下:

def fibs(n):
   if n == 0 or n == 1:
      return 1
   else:
      return fibs(n-1) + fibs(n-2)


print([fibs(i) for i in range(10)])

自定义一个迭代器, 实现斐波那契数列

class Fib2(object):
    def __init__(self, n):
        self.a = 0
        self.b = 1
        self.n = n
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        res = self.a
        self.a, self.b = self.b, self.a + self.b
        if self.count > self.n:
            raise StopIteration
        self.count += 1
        return res

print(list(Fib2(10)))

其他迭代器案例

(1) 有很多关于迭代器的例子,比如itertools函数返回的都是迭代器对象。

def test_endless_iter():
   '''
   生成无限序列
   :return:
   '''
   from itertools import count
   counter = count(start=3)
   print(next(counter))
   print(next(counter))
   print(next(counter))  

(2) 从一个有限序列中生成无限序列

def test_cycle_iter():
   '''
   从一个有限序列中生成无限序列
   :return:
   '''
   from itertools import cycle
   colors = cycle(['red', 'white', 'blue'])
   print(next(colors))
   print(next(colors))
   print(next(colors))
   print(next(colors))

总结:

 对于list、string、tuple、dict等这些容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数。iter()是python内置函数。 

iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。

  迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的next方法(Python3中是对象的next方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的next方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现iter方法,而iter方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的iter方法返回自身self即可。

生成器

生成器作用?
延迟操作。也就是在需要的时候才产生结果,不是立即产生结果。

什么是生成器?
生成器是包含yield关键字的函数。本质上来说,关键字yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。

生成器调用顺序

那么,生成器是怎么调用执行的呢?只需要了解下面几条规则即可:

a. 当生成器被调用的时候,函数体的代码不会被执行,而是会返回一个迭代器,其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。如同迭代器一样,我们可以使用next()函数来获取下一个值。需要明白的是,这一切都是在yield内部实现的。

b. 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止

next()方法的返回值就是yield语句处的参数(yielded value)

当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常。

c.每调用一次生成器的next()方法,就会执行生成器中的代码,知道遇到一个yield或者return语句。yield语句意味着应该生成一个值(在上面已经解释清楚)。return意味着生成器要停止执行,不在产生任何东西。

d. 生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环器,每次循环使用一个yield返回的值。

注意:

(1)生成器是只能遍历一次的。

(2)生成器是一类特殊的迭代器。

分类:

第一类:生成器函数:
还是使用 def 定义函数,但是,使用yield而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行。

如下案例加以说明:

def Fib3(max):
   n, a, b = 0, 0, 1
   while n < max:
      yield b
      a, b = b, a + b
      n = n + 1
   return '亲!没有数据了...'


print("@@@@@@@@@@@@@222")
# 调用方法,生成出10个数来
f=Fib(10)
# 使用一个循环捕获最后return 返回的值,保存在异常StopIteration的value中
while  True:
   try:
      x=next(f)
      print("f:",x)
   except StopIteration as e:
      print("生成器最后的返回值是:",e.value)
      break

第二类:生成器表达式:
类似于列表推导,只不过是把一对大括号[]变换为一对小括号()。但是,生成器表达式是按需产生一个生成器结果对象,要想拿到每一个元素,就需要循环遍历。

如下案例加以说明:

# 一个列表
xiaoke=[2,3,4,5]
# 生成器generator,类似于list,但是是把[]改为()
gen=(a for a  in xiaoke)
for  i  in gen:
    print(i)
#结果是:
2
3
4
5

为什么要使用生成器?因为效率。使用生成器表达式取代列表推导式可以同时节省 cpu 和 内存(RAM)。如果你构造一个列表(list)的目的仅仅是传递给别的函数, 比如 传递给tuple()或者set(), 那就用生成器表达式替代吧!

iter

如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def next(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration();
        return self.a # 返回下一个值

现在,试试把Fib实例作用于for循环:

>>> for n in Fib():
...     print n
...
1
1
2
3
5
...
46368
75025

getitem

要表现得像list那样按照下标取出元素,需要实现getitem()方法:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a

现在,就可以按下标访问数列的任意一项了:

>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

但是list有个神奇的切片方法:

>>> range(100)[5:10]
[5, 6, 7, 8, 9]

对于Fib却报错。原因是getitem()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

现在试试Fib的切片:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

但是没有对step参数作处理:

>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

也没有对负数作处理,所以,要正确实现一个getitem()还是有很多工作要做的。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!