迭代器
迭代 是访问集合元素的一种方式。迭代器 是一个可以记住遍历的位置的对象。迭代器 对象从集合的第一个元素开始访问,直到所有的元素被访问结束。迭代器 只能往前不会后退。
1. 可迭代对象
我们已经知道可以对 list、tuple、str 等类型的数据使用 for...in... 的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫 迭代。
但是,是否所有的数据类型都可以放到 for...in... 的语句中,然后让 for...in... 每次从中取出一条数据提供我们使用,即供我们迭代吗?
In [1]: for i in 100:
...: print(i)
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-86150fa0c47d> in <module>
----> 1 for i in 100:
2 print(i)
3
TypeError: 'int' object is not iterable
# int 整型不是iterable,即int整型不是可以迭代的 # 我们自定义一个Classmate类用来存放数据,可以通过add方法向其中添加数据
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
# 运行结果:
TypeError: 'Classmate' object is not iterable
# Classmate() 对象也是不可以迭代的。
2. isinstance: 判断是否可以迭代?
In [1]: from collections import Iterable
In [2]: isinstance([11,22,33], Iterable)
Out[2]: True
# 只要返回值为True,就意味着可以迭代
In [3]: isinstance("abcdef", Iterable)
Out[3]: True
In [4]: isinstance((100,200,300), Iterable)
Out[4]: True
In [5]: isinstance(100, Iterable)
Out[5]: False
3. 迭代器
实际开发中,往往出现一种场景。创建出的对象 (classmate),这个对象里面有个属性(self.names),这个列表里面添加了很多东西,能不能用 for 去这个对象里面的值呢?可以!怎么做呢?
1. 在创建这个对象的类里面必须实现 __iter__ 方法
2. __iter__ 方法 必须返回一个迭代器的对象引用,什么是迭代器呢?只要这个类里面有__iter__ 方法和 __next__ 方法,就称这样的类创建出来的对象叫做迭代器,他的一大特点就是可以取里面的值。(返回的是哪一个对象的引用,接下来for循环在取值的时候实际上就去取这个对象里面的 __next__ 方法的返回值,__next__ 返回什么,接下来就当 for 循环的那个值是什么。)
4. 代码实现
import time
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
"""
如果让一个对象成为一个可以迭代的对象,即可以使用for,
那么必须实现__iter__方法
"""
return ClassIterator(self)
"""把这个对象(ClassIterator)的引用返回了,直白地讲就是得到了这个
对象的引用,得到了这个对象的引用,实际上for循环就会调用它
(ClassIterator)的__next__方法,调一次取一个,放到temp里面
打印,每for一次都会调用一次 __next__ ,__next__ 返回什么,
就给temp什么。"""
"""
言外之意是我创建的是Classmate的对象,把这个对象的引用
Classmate()放到for循环后面去了,但是在最终实现的过程中,
你这个方法(__iter__)返回的是哪一个对象的引用,那么将来在for
循环的整个过程中,它就调用返回的对象里面的 __next__ 方法,
__next__ 返回什么,for就看见什么
"""
class ClassIterator(object): # 迭代器
def __init__(self, obj):
self.obj = obj
self.current_num = 0
def __iter__(self):
pass
def __next__(self):
if self.current_num < len(self.obj.names):
ret = self.obj.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
time.sleep(1)
5. 代码升级
import time
class Classmate(object):
def __init__(self):
self.names = list()
self.current_num = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
"""如果让一个对象成为一个可以迭代的对象,即可以使用for,
那么必须实现__iter__方法"""
return self # 返回自己
def __next__(self):
if self.current_num < len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
"""不会再满足,else就执行,raise StopIteration就意味着产生一
个异常,产生一个异常因为并没有try来处理,产生异常自动的传递到
for循环这个地方来处理,for循环自动的检测到StopIteration之后,
停止了迭代"""
else:
raise StopIteration
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
time.sleep(1)
6. for...in...本质
for item in Iterable 循环的本质就是先通过 __iter__() 函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用 __next__() 方法来获取下一个值并将其赋值给 item ,当遇到 StopIteration 的异常后循环结束。
怎么告诉 for 循环我已经取(迭代)完了呢?
通过产生一个自定义抛出一个异常 StopIteration,自定义抛出异常就解决了这个问题!
7. 迭代器的应用场景
举个例子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
现在我们想要通过for...in...循环来遍历迭代斐波那契数列中的前n个数。那么这个斐波那契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。
class Fibonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1
def add(self, name):
self.names.append(name)
def __iter__(self):
"""如果让一个对象成为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
return self
def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a + self.b
self.current_num += 1
return ret
else:
raise StopIteration
fiber = Fibonacci(10)
for name in fiber:
print(name)
8. 并不是只有for循环能接收可迭代对象
除了for循环能接收可迭代对象,list、tuple等也能接收。
li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)
元组 FibIterator(15),list 方法可以将他转成列表;
列表 FibIterator(6),也可以通过 tuple 方法转成 元组,到底是怎么做的呢?
并不是简单地转换类型,而是通过迭代的方式取里面的值,取一个生成一个。比如
In [1]: a = (11,22,33)
In [2]: list(a)
Out[2]: [11, 22, 33]
它是怎么做的呢?先生成一个空列表,接下来调用 list(a) 里面的迭代器 ,因为 a 是一个迭代对象,他通过 __iter__ 函数找到了里面的迭代器,找到了迭代器之后通过 __next__ 取里面的值,就完成了。