对python迭代器和可迭代对象的一些理解

__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()。其作用是:

  1. 检查对象是否实现了__iter__方法,如果实现了就调用它,获得一个迭代器。

  1. 如果没有实现,但实现了__getitem__,python会创建一个迭代器,尝试按索引来获取元素

  1. 如果尝试失败,抛出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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值