迭代器

迭代
  可以使用hasattr函数来检测一个对象是否是可迭代的,即检查对象中是否有__iter__方法。

>>> hasattr(list, '__iter__')    # 列表可迭代
True
>>> hasattr(int, '__iter__')    # 整型不可迭代
False

   __iter__是一个特殊方法,它是迭代规则的基础,有了它,就说明对象是可迭代的。
    跟迭代有关的一个内建函数iter(),它返回一个迭代器对象。

>>> lst = list('python')    # lst是可迭代的
>>> hasattr(lst, '__iter__')
True
>>> hasattr(lst, '__next__')    # lst中没有__next__方法,不是迭代器对象
False
>>> iter_lst = iter(lst)    # 由lst产生一个迭代器对象iter_lst,等同于iter_lst = lst.__iter__()
>>> hasattr(iter_lst, '__iter__')
True
>>> hasattr(iter_lst, '__next__')    # iter_lst中有__next__属性,是迭代器对象
True

  可迭代的对象中有__iter__方法,但不一定是迭代器对象;迭代器对象一定是可迭代的,它同时拥有__iter__方法和__next__方法。
  迭代器对象要遵循迭代协议,即实现了__next__方法。我们可以直接在类中定义__next__方法以实现迭代器协议,也可以通过执行对象名.__iter__()的方式实现迭代器,还可以通过执行python的内建函数iter(),即iter(对象名)的方式实现迭代器对象。

 

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

>>> class MyRange:
...     def __init__(self, n):
...         self.i = 1
...         self.n = n
...     def __iter__(self):    # 实现__iter__方法
...         return self
...     def __next__(self):    # 实现__next__方法
...         if self.i <= self.n:
...             i = self.i
...             self.i += 1
...             return i
...         else:
...             raise StopIteration    # 条件结束,必须返回StopIteration异常
... 
>>> x = MyRange(7)
>>> print([i for i in x])
[1, 2, 3, 4, 5, 6, 7]
>>> print(hasattr(x, '__iter__'))    # 自定义的迭代器对象中有__iter__方法和__next__方法
True
>>> print(hasattr(x, '__next__'))
True

  以上代码是仿写类似range()的类,但它跟range()又有所不同,它是一个迭代器对象,有以下特点:
  __iter__()方法是类中的核心,它返回了迭代器本身。一个实现了__iter__方法的对象,就意味着它是可迭代的。
  实现了__next__()方法,使得这个对象是迭代器对象。可迭代的对象不一定是迭代器对象,如列表、元组等;迭代器对象一定可迭代。
  再实现一个斐波那契数列的迭代器对象。利用迭代器对象实现斐波那契数列的优点是节省内存,迭代器对象的斐波那契数列不会将数列的所有值都存放在内存中,只会一个一个地取出,而且取出了后一个,前一个值不会保存在内存值。

>>> class Fibs:
...     def __init__(self, max_num):
...         self.max_num = max_num
...         self.a = 0
...         self.b = 1
...     def __iter__(self):
...         return self
...     def __next__(self):
...         fib = self.a
...         if fib > self.max_num:
...             raise StopIteration
...         self.a, self.b = self.b, self.a + self.b
...         return fib
... 
>>> fibs = Fibs(7)
>>> print(list(fibs))
[0, 1, 1, 2, 3, 5]

  结合上面的斐波那契数列的迭代器对象,对迭代器做概括:
    1.在python中,迭代器是遵循迭代协议的对象。
    2.可以使用iter()函数从任何序列得到迭代器(如list、tuple、dict等)。
    3.自己编写迭代器对象,即编写类,其中实现__iter__()和__next__()方法。当没有元素时,引发StopIteration异常。
    4.如果有很多值,列表会占用太多的内存,而迭代器则占用更少内存,因为__next__机制,执行一次给一个值,内存当中只会保存当前__next__给定的值,前面的值不会被保存,所以节省内存空间。
    5.迭代器从第一个元素开始访问,直到所有元素被访问完结束,只能往前,不能后退。且迭代器中的元素是一次性的,只能被取出一次。

>>> lst = [x for x in range(7)]    # 列表解析式的结果依然是列表对象
>>> lst
[0, 1, 2, 3, 4, 5, 6]
>>> lst    # 可以无数次被调用
[0, 1, 2, 3, 4, 5, 6]
>>> tu = (x for x in range(9))    # 元组解析式是一个迭代器对象
>>> tu
<generator object <genexpr> at 0x10218a1b0>
>>> list(tu)    # 迭代器对象中的内容只能被调用一次,是一次性的
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> list(tu)    # 迭代器对象只能向前,不能后退
[]
>>> tu2 = (x for x in range(3))
>>> tu2.__next__()    # 迭代器对象就是通过调用它的__next__方法来取值
0
>>> tu2.__next__()
1
>>> tu2.__next__()
2
>>> tu2.__next__()    # 没有元素则抛出StopIteration异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> tu3 = (x for x in range(3))
>>> next(tu3)    # 通过内置函数来取出迭代器中的元素
0
>>> next(tu3)    # next(tu3)就相当于执行tu3.__next__()
1
>>> next(tu3)    # 内置函数next()就是在执行迭代器中的__next__方法
2
>>> next(tu3)    # 没有元素则抛出StopIteration异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> next(tu3, ‘aa’)    # next()函数可提供一个默认值,没有元素时输出默认值
aa

 

for循环和迭代器:
  对一个可迭代对象执行for循环遍历,首先for循环会对对象调用iter()函数,iter()函数会返回一个定义了__next__方法的迭代器对象,再通过对对象调用next()函数(next()函数会执行对象内部的__next__方法)来逐个访问对象的内容。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。

>>> l = list('python')    # 可迭代的列表对象,对象中有__iter__方法
>>> for i in l:    # 执行for循环,会对对象l调用iter()函数,返回一个迭代器对象
...     print(i, end=' ')    # 然后再对迭代器对象调用next()函数取值
... 
p y t h o n

 

posted @ 2018-11-13 17:24  从python开始  阅读(264)  评论(0编辑  收藏  举报