在python中,一切都是对象,什么样的对象可以迭代。或者说这个对象可以迭代是不是因为他包含了什么特殊的属性?
回想下上一篇,for in循环做了什么,好像是4步,其实只有2步。
第一步,判断是否可迭代,iterable通过iter转换为iterator的过程。
第二步执行next到结束(如果我们类里包含了关于next结束的判断,就不会报错了,所以肯定是没有第三步的)。
那么类里面"必然"包含2个部分,iter和next。
class Fib:
'''iterator that yields numbers in the Fibonacci sequence'''
def __init__(self, max):
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
这是从官网抄的一个例子,很漂亮。为了方便我就直接写for循环调用了,不太理解的跟上文连起来看。
fib = Fib(1000)
for n in fib:
print(n, end=' ')
print()
print(iter(fib))
print(fib)
结果:首先打印出来的是菲波那切数列,这个无疑问了,最后的两行是一样的都是self。
分析:
我们先进行了fib的实例化,然后调用了for循环,因为没有报错,说明fib是可调用的(iterable),然后我们对fib进行了iter方法,生成了iterator。后面的打印告诉我们,我们之前的fib(iterable)和生成的iterator是相同的,因为我们类中__iter__返回的是self本身,所以是相同的。最后我们进入了循环即调用__next__。
iterable和iterator的官方定义
iterable
能够一次返回自身的成员的对象是可迭代的。比如一切的序列类型(list,str,tuple)还有一些非序列类型dict,file object,和一切定义了__iter__方法或者__getitem__方法的类,他们执行了序列的语法。
可迭代对象能被用在for循环中和在其他的一些需要的地方比如zip(), map()。当一个可迭代对象被作为一个参数传递给iter()的时候,他返回了这个对象的迭代器(iterator)。这个迭代器擅长逐个取出对象中的值。当使用可迭代对象的时候,通常不需要调用iter(),或处理迭代器对象,for声明会自动为你处理的,在循环期间创建一个临时的未命名变量保存在迭代器中。详细请看iterator,sequence,generator
iterator
一个数据流对象,重复调用迭代器的__next__()方法(或者是next()),从数据流中逐次的返回数据。如果没有更多可用的数据,将抛出一个StopIteration。在这个点上,以后怎么调用next(),都会再次抛出StopIteration。迭代器需要有__iter__()方法,这个方法用来返回这个迭代器对象本身,所以每一个迭代器(iterator)都是可迭代的(iterable),同样,他可以在大多数情况下被当作可迭代的对象使用。我们应该注意尝试多次迭代传递这样的代码。一个容器(container)对象(比如list)在你将他传递给iter()或者用for循环的时候产生了一种新的迭代器。用迭代器尝试他,只会返回上次迭代器传递中使用的相同的已经耗尽的迭代器对象,使其看起来像个空容器。我理解蓝字的意义就是迭代器被iter()或者for调用生成的还是迭代器,生成的这个和原来的是一样的,所以不断地用iter()调用一个迭代器是没意义的。(英文不好,错了请指正)
总结一下iterable和iterator,就2点
1.序列类型,定义了__iter__和__getitem__方法的类是可迭代的。
2.调用迭代器用next()或者__next__()
接下来主要讲讲这两个问题
什么是序列
首先他是一个迭代器,通过__getitem__方法使用整数索引进行有效的元素访问。并定义__len__方法,__len__方法返回序列的长度。
说明索引用的是__getitem__方法。
那么我们可以用__iter__和__getitem__来定义可迭代对象了?
class A:
def __getitem__(self, index):
if index >= 10:
raise IndexError
return index * 111
for i in A():
print(i)
class B:
def __iter__(self):
yield 10
yield 20
yield 30
for i in B():
print(i)
从上面的例子可以看出,这2个例子都没有next(),__getitem__调用next实际调用了他本身,不会考虑__next__,而iter()调用的是yield。
如果合在一起,会怎么样?
class C:
def __getitem__(self, index):
if index >= 10:
raise IndexError
return index * 111
def __iter__(self):
yield 10
yield 20
yield 30
for i in C():
print(i)
第三个例子说明同时有__iter__和__getitem__的时候,不考虑__getitem__。
既然可以没有__next__,我们在反过来考虑下,可不可以没有__iter__?
class D():
def __init__(self,data=1):
self.data = data
def __next__(self):
if self.data > 5:
raise StopIteration
else:
self.data+=1
return self.data
t = D(3)
# for i in t:
# print(i)
for i in range(3):
print(t.__next__())
貌似也可以,不过我们发现,这里t并不是一个迭代器,注释部分报错了,后面部分成功是因为range(3)是可迭代的,作用是读取个数,有点类似我上一节说的读取个数的循环。t只有__next__方法。
所以,迭代器是必须有__iter__的,也可以说,__iter__规定了他可以是可迭代的,但是可以没有__next__。
同样,我们会发现有些类只有__iter__,没有__next__,是不能for循环的,个人理解,他作为一个可迭代的,但是没有序列这种格式的语法。如果有,比如yield这样是可以的。
如何检测迭代器?
1.用collections模块
这个模块提供了一些基础的类,我们可以通过是否是实例来判断
import collections
if isinstance(e, collections.Iterable):
# e is iterable
这个不能判断__getitem__
2.用生成器
try:
_ = (e for e in my_object)
except TypeError:
print my_object, 'is not iterable'
其实你可以理解为直接用了for循环
3.用iter()
try:
some_object_iterator = iter(some_object)
except TypeError as te:
print some_object, 'is not iterable'
yield
这个算是附加吧,我们也可以看到,一般情况下什么样的函数是可以迭代的。
下面我们用一个例子,来说明yield:
def foo():
print("begin")
for i in range(3):
print("before yield", i)
yield i
print("after yield", i)
print("end")
print(foo().__iter__)
f = foo()
next(f)
# for i in f:
# print('here', i)
这个例子推荐在python命令行一行一行输入。因为这样你可以更好的发现问题。
这个函数他的__iter__已经定义过了,为了避免你有以为,我提前调用了。
首先执行f =foo()
这个时候begin是没有出现的
如果你是下面的代码:
def foo():
print('begin')
f = foo()
会有begin的
其次你输入next(f)
begin
before yield 0
我们发现输出在yield之前的内容
再次输入next(f)
after yield 0
before yield 1
我们发现确实是在走循环,但是yield的内容去哪了?
这次输入print(next(f))
after yield 1
before yield 2
2
我们看到yield的值作为返回值传出来了。
yield的工作模式应该已经清楚了。