__iter__:用来返回一个迭代器对象
__next__要么返回迭代器对象里的下一个item,要么抛出异常StopIteration。
这里非常重要的是,这两个函数具体写法在不同情境下是不同的。
迭代器:类定义内有__iter__和__next__的对象。
可迭代对象:类定义内有__iter__的对象。(注意这里__iter__也是用来返回一个迭代器对象的,但是函数写法和迭代器里的__iter__写法是有区别的)
先看迭代器的例子。
class Range(object):
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
return self.idx
self.idx = self.idx + 1
else:
raise StopIteration()
Range = Range(3)
print(Range.__next__())
print(Range.__next__())
这里输出为
0
0
为什么是两个0?难道不能迭代吗?
其实是因为return的性质,当return返回了一个值后,在return下面的函数语句将不会被执行,所以Range里的idx的值不会发生变化,一直是0。
我们对__next__函数稍作修改。
def __next__(self):
if self.idx < self.n:
val=self.idx
self.idx = self.idx + 1
return val
else:
raise StopIteration()
(注意引入了变量val来储存self.idx的值,这样self.idx可以放心变化。否则的话要么self.idx=self.idx+1在return self.idx之前,要么在其之后。
前者无法遍历第一个元素,后者一直输出0.而val又只局限在函数内部,不会影响外部)
输出:
0
1
即迭代器起作用了。
值得一提的是,我们迭代的过程其实就是反复调用Range.__next__()这一函数。迭代器能起作用,本质上是因为我们迭代的item是类的属性,即这里的self.idx。这个变量是储存在类里面的,本质上具有“记忆”功能,Range调用了实例方法__next__(),并把自己赋值给这个方法的参数,这样self.idx本质上就是Range.idx,在不受外界影响的情况下是不变的。这点是非常重要的。
到这里为止,似乎迭代器满足我们的迭代需求了,那为什么还要引入可迭代对象这一概念呢?
实际上观察Range的__next__()方法我们可以看出,self.idx是不断增加的,方法内没有任何重新设置idx值的语句,即迭代器本身是个一次性的对象,迭代完一次就不能使用了。为了能够重复使用,我们于是引入可迭代对象,并将两者分离。
class a():
def __init__(self,n):
self.n=n
def __iter__(self):
return aiter(self.n)
class aiter():
def __init__(self,n):
self.n=n
self.idx=0
def __iter__(self):
return self
def __next__(self):
if self.idx<self.n:
val=self.idx
self.idx=self.idx+1
return val
a1=a(5)
a2=a1.__iter__()
a3=a1.__iter__()
print(a2.__next__())
print(a2.__next__())
print(a3.__next__())
print(a3.__next__())
如上,我们先创建一个可迭代对象,即a1,然后调用了两次a1.__iter__()函数,返回了两个迭代器对象,并分别把这两个对象赋值给a2,a3.这样后续就可以使用这两个迭代器对象了。
输出结果为
0
1
0
1
for语句本质上,也是这样,首先调用可迭代对象的__iter__()函数,返回一个迭代器对象,然后不断地调用__next__()函数,从而实现迭代。
可是这样又有疑问了,可迭代对象需要__iter__()来返回迭代器,这是没有问题的,那么迭代器本身为什么也要这个函数,这不是多此一举吗?
其实,迭代器实现的__iter__是为了兼容可迭代对象。比如for对可迭代对象的流程是先调用函数,返回迭代器,然后对返回的迭代器对象调用函数。为了是这个流程也适应迭代器对象,我们于是也设置了__iter__().这样for语句对两者都可以适用了。
此外补充一个例子。
class Range(object):
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()
Range = Range(3)
print([i for i in Range])
print([i for i in Range])
输出
[1, 2, 3]
[]
注意:这里[i for i in Range]是遍历range里的元素来创建一个新的列表。
此外python还有内置函数next()和iter()
当解释器需要迭代对象x时,会自动地调用iter()。其作用是:
检查对象是否实现了__iter__方法,如果实现了就调用它,获得一个迭代器。
如果没有实现,但实现了__getitem__,python会创建一个迭代器,尝试按索引来获取元素
如果尝试失败,抛出TypeError,即对象是不可迭代的。
next()的作用是Retrieve the next item from the iterator by calling its __next__() method. If default is given, it is returned if the iterator is exhausted, otherwise StopIteration is raised.
所以next()可以代替self.__next__()
如果一个函数中包含yield,那么它就是一个生成器函数,调用它能返回一个生成器对象。
而生成器对象就是迭代器对象。
def generator():
yield 'a'
yield 'b'
yield 'c'
for _ in generator():
print(_)
generator1=generator()
print(next(generator1))
print(next(generator1))
输出
a
b
c
a
b
generator是一个函数对象,而它调用的结果generator()是一个生成器。
个人觉得yield其实就是免去了一系列繁琐的产生元素的代码,直接呈现了最终结果。即
if self.idx < self.n:(其中self.idx的初始值被设置为0)
val = self.idx
self.idx += 1
这一段代码就是输出[0,n)的数,而我们现在直接把代码的结果,即[0,n)的数赋给了yield表达式,这样不仅简洁了,而且返回对象的类型变得更加自由。
我们重新审视一下下面这段代码。
def generator():
yield 'a'
i=yield 'b'
print(i)
yield 'c'
for _ in generator():
print(_)
generator()调用了generator这个函数,然后返回了一个生成器对象(属于迭代器对象),然后for语句调用了这个生成器对象的__iter__方法,从而得到了迭代器,然后根据next()定义,是一定要得到返回值的,所以第一次next,函数开始执行,直至遇到第一个yield,停止并返回yield后的值‘a’,这个值被赋给了_,并且被打印出来。第二次next()从上一次yield停止的地方开始,继续执行,遇到了第二个yield‘b’,同样被打印。第三次,从刚才停止的时候继续执行,因为‘b’已经被返回了,i没有被赋值,所以print了none,继续执行,yield‘c’。
实际上这个for可以直接用如下代码段代替
a=generator()
print(next(a))
print(next(a))
print(next(a))
最终输出结果
a
b
None
c