迭代
可以使用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