可迭代对象,迭代器的介绍

可迭代对象

参考链接:https://www.cnblogs.com/eastonliu/p/9156418.html
Python中可迭代对象(Iterable)并不是指某种具体的数据类型,它是指存储了元素的一个容器对象,且容器中的元素可以通过__iter__( )方法或__getitem__( )方法访问。事实上python有很多内置方法的类就是可迭代对象,比如list、set、tuple等内置类所建的对象都可以说是可迭代对象,因为它们都实现了__iter__()方法或者__getitem__()方法。
接下来,我们可以进入list类的源码中去查看,这两个类:

 def __iter__(self, *args, **kwargs): # real signature unknown
        """ Implement iter(self). """
        pass
 
 def __getitem__(self, y): # real signature unknown; restored from __doc__
        """ x.__getitem__(y) <==> x[y] """
        pass

刚才我们说,“容器中的元素可以通过__iter__( )方法或__getitem__( )方法访问”,这句话听起来似乎有一点难以理解。我们访问一个可迭代对象比如列表,我们一般时通过索引访问值的如下面代码所示:

arr = [1,2,3,4]
print(arr[1])
#结果为:2

list也是一个类,类体里面也有很多方法,特别是魔法方法,但是并不是说他天生就是一个可迭代对象,因为它内部的3个重要的魔法方法使它成为了可迭代对象。如果不产生循环,或者不用iter()函数的话,list就单纯的只是一个可迭代对象,还不是一个迭代器,因为只有通过for循环,或者iter()才能使我一个列表变成迭代器,进而才能进行遍历。__iter__函数和__getitem__函数的源码是c写的,我们无法通过源码去观察怎么使list类成为迭代器。不过,既然list是类,那么我们也可以通过自己构建一个类,去构建一个可迭代对象去存储元素。

__iter__方法的作用是让对象可以用for … in循环遍历。比如当开始循环时,首先会调用循环对象的那个类的__iter__方法,然后就会自动获取可迭代对象的迭代器,此时,可迭代对象就转型成为了迭代器,然后下一步,根据这个对象的__next__()中的条件,去消耗可迭代对象中的元素。__getitem__方法是让对象可以通过实例名[index]的方式访问实例中的元素。事实上,通过这两个方法去访问可迭代对象的元素,也可以理解为Python实现一个通用的外部可以访问可迭代对象内部数据的接口。下面通过<<流畅的python>>中的代码案例来解释:

import re
import reprlib

RE_WORD = re.compile(r"\w+")


class Sentence(object):
    def __init__(self, text):
        """
        self.words = ['go', 'to', 'bed', 'early', 'and', 'get', 'up', 'early', 'TRUNP', 'said']
        :param text:
        """
        self.text = text
        self.words = RE_WORD.findall(text)  # 相当于把每个单词给挑选出来,然后放到一个数组里面

    def __getitem__(self, index):
        print("这个inde参数的值为:{index}".format(index=index))
        return self.words[index]

    def __len__(self):
        return len(self.words)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)


if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    print(s[1], s[2], s[3])
    #输出结果为:这个inde参数的值为:1
	#这个inde参数的值为:2
	#这个inde参数的值为:3
to bed early

从结果来看,__getitem__方法是让对象可以通过实例名[index]的方式访问实例中的元素,而且__getitem__函数中的index参数正式我们在"main"函数里的索引s[1],s[2],s[3]。这正是我们通过“可迭代对象”结合索引获取迭代器中的元素的一个原理。有人可能会觉得奇怪,__getitem__函数里也是通过“可迭代对象”结合索引获取值的,这岂不是通过这个原理再去证明这个原理吗?事实上,比如,我们点开list类的__getitem__这个函数,我们发现函数体中的内容已经被pass掉了,因为这一步是用c写的,我们只能知道实现的原理,然后用python所拥有的语法去复现这个原理,所会出现“自己证明自己”的原因。
之前提过,在Python中,一个可迭代对象是不能独立进行迭代的,我你需要获取这个元素的迭代器才能进行迭代。我们遍历可迭代对象中的元素,说白了就是去消耗这个容器中的元素,我们可以一个一个去消耗,也可以每隔一个元素去消耗,这可以理解为消耗元素的规则,可以由我们自己去定义,如果超出范围,就会报StopIteration异常。这个时候我们就可以进行循环了,无非通过两种方式,一种是for循环,另一种是while循环。先说for循环:

import re
import reprlib
RE_WORD = re.compile(r"\w+")

class Sentence(object):
    def __init__(self, text):
        """
        self.words = ['go', 'to', 'bed', 'early', 'and', 'get', 'up', 'early', 'TRUNP', 'said']
        :param text:
        """
        self.count = 0
        self.text = text
        self.words = RE_WORD.findall(text)  # 相当于把每个单词给挑选出来,然后放到一个数组里面

    def __repr__(self):
        """
        建立对象时,会触发此函数,并将类的描述信息返回实例变量s
        :return:
        """
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # return iter(A((4,3,2,4,3,5,6,7,4,2,4,6,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,1,1,1,1)))
        return self

    def __next__(self):
        self.max_length = len(self.words)

        if self.count < self.max_length:
            aa = self.count
            self.count = self.count + 1
            return self.words[aa]
        else:
            raise StopIteration


if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    print(type(s))
    for i in s:
        #print(next(s))
        print(i)
      

结果:

<class '__main__.Sentence'>
go to bed early and get up early TRUNP said

Sentence这个类已经有了一个特殊的方法了,即__iter__,使它完全有资格进行迭代。创建对象这一块很好解释,直接跳过。当我们对s这个引用变量进行for循环的那一刻,这个可迭代对象就“自动”获取了迭代器,因为会触发__iter__这个魔法函数,python要求,__iter__的返回结果一定要是一个迭代器(self为什么是迭代器?因为当前类已经实现了iter方法),并且在循环的时候,可以调用__next__()来消耗元素)。Sentence类实例化后,就是一个迭代器,所以for循环会自动获取迭代器。那么就有资格进行迭代了。for循环获取下一个元素其实也是通过__next__,只是for循环和while循环进行相比,调用__next__并没有显得那么明显,我们把上面得代码中的__next__函数稍微改造下,其余不变:

    def __next__(self):
        self.max_length = len(self.words)

        if self.count < self.max_length:
            aa = self.count
            self.count = self.count + 1
            print("用于for循环测试")
            return self.words[aa]
        else:
            raise StopIteration

我这里只加了一句测试代码进行输出,

<class '__main__.Sentence'>
用于for循环测试
go
用于for循环测试
to
用于for循环测试
bed
用于for循环测试
early
用于for循环测试
and
用于for循环测试
get
用于for循环测试
up
用于for循环测试
early
用于for循环测试
TRUNP
用于for循环测试
said

从结果中可以看出,for循环每循环一次,都会调一次__next__方法,这就证明,for循环也是通过__next__去遍历元素的,我们也可以显示的去用__next__或者next()方法消耗元素,这两个函数也可以理解为一个函数:

if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    for i in s:
        print(next(s))
        # print(s.__next__())

结果为:

用于for循环测试
用于for循环测试
to
用于for循环测试
用于for循环测试
early
用于for循环测试
用于for循环测试
get
用于for循环测试
用于for循环测试
early
用于for循环测试
用于for循环测试
said

结果和隐式调用的结果并不一样,很明显结果对不上----隔一个元素才输入一个元素,以及"用于for循环测试"这段代码每次都要输出两遍,因为next(s)会影响for循环隐式调用__next__方法。第一轮循环,for循环的机制会默认把指针指向第一个元素,如果我们把“print(next(s))”这段代码去掉,直接print(next(i))后,第二轮循环,就会把指针指向第二个元素。也正式因为这样,我们手动添加的next(s)这个函数把for循环指向第一个元素的指针又指向了第二个元素,后面几轮循环也类似,知道把所有元素消耗完。所以输出结果中才出现隔一个元素才输入一个元素。而"用于for循环测试"这段代码要输出两遍得这个原因更好理解了,for循环对__next__函数的隐式调用,以及我们手动写的next()方法都会触发__next__函数,所以"用于for循环测试"这段代码要输出两遍

交叉输出迭代的元素以及"用于for循环测试"这句话。刚才我们说,调用__next__或者next()方法都行,
上面代码中,我们注释了其中一个代码,那么假如我们把注释给放开会出现什么结果呢?试一下:

for i in s:
        print(next(s))
        print(s.__next__())

结果为:

Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/untitled/生成器函数.py", line 42, in <module>
<class '__main__.Sentence'>
用于for循环测试
用于for循环测试
to
用于for循环测试
bed
用于for循环测试
用于for循环测试
and
用于for循环测试
get
用于for循环测试
用于for循环测试
early
用于for循环测试
TRUNP
用于for循环测试
    print(next(s))
  File "C:/Users/Administrator/PycharmProjects/untitled/生成器函数.py", line 35, in __next__
    raise StopIteration
StopIteration

这个结果给我们第一感觉是程序报错了。实际上,一轮循环过后,指针先后指向了前面3个元素,第二轮循环指向了第四了个,所以,我会发现,总有两个单词是连着的,比如"to bed",“and get”。而“TRUNP”和“said”本来也应该连着的,却因为迭代器中的元素耗尽,指针指向下一个不存在的值后会爆出StopIteration异常。
那我们能不能像循环列表一样,把所有元素迭代完成后再迭代一遍呢?

a = [1,2,3,4]
for i in a:
    print(i)
for b in a:
    print(b)
if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    for i in s:
        print(i)
    for a in s:
        print(a)

结果为:

go
用于for循环测试
to
用于for循环测试
bed
用于for循环测试
early
用于for循环测试
and
用于for循环测试
get
用于for循环测试
up
用于for循环测试
early
用于for循环测试
TRUNP
用于for循环测试
said

通过结果发现,并不能,原因是可迭代对象是不可逆的,一旦元素耗尽,则无法再次循环,除非再定义一个可迭代对象实例。。。

if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    for i in s:
        print(i)
    a  = Sentence("'go to bed   early and get up early',TRUNP said")
    for aa in iter(a):
        print(aa)

上文我们也提过,list,dict,set等一系列可迭代对象,因为它们可以迭代,那么这些类中必然也有__iter__,和__getitem__等方法,我们可以重新定义一个类去继承这些类,并重写这两个方法去观察,看看是不是和我们之前自定义的可迭代对象一样:

class A(list):
    def __iter__(self):
        print("哈哈")
        return super().__iter__()

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)


test_list = A([1,2,3])
for i in test_list:
    print(i)

print("通过变量加索引的方式去调用:",test_list[1])

结果为:

哈哈
1
2
3
itemm的值为: 1
通过变量加索引的方式去调用: 2

通过结果,不难发现,我们在循环A的时候,确实调用__iter__方法,这又和我们之前的观点重合,for循环对__inter__是隐式调用的。
而通过变量加索引去访问可迭代对象中的元素,也可以发现,__getitem__中的item就是我们刚才写的index,和之前预料的也一样,真正懂得后,不管是自定义还是继承重写可迭代对象,都无所谓,很灵活。

刚才讲,for语句在循环时,会自动调用__inter__,获得迭代器才能迭代元素,那while呢?是不是同样这个原因呢?先说结论,不是,事实上,while迭代元素更依赖__next__方法:

import re
import reprlib
RE_WORD = re.compile(r"\w+")

class Sentence(object):
    def __init__(self, text):
        """
        self.words = ['go', 'to', 'bed', 'early', 'and', 'get', 'up', 'early', 'TRUNP', 'said']
        :param text:
        """
        self.count = 0
        self.text = text
        self.words = RE_WORD.findall(text)  # 相当于把每个单词给挑选出来,然后放到一个数组里面

    def __repr__(self):
        """
        建立对象时,会触发此函数,并将类的描述信息返回实例变量s
        :return:
        """
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # return iter(A((4,3,2,4,3,5,6,7,4,2,4,6,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,1,1,1,1)))
        print("哈哈")
        return self

    def __next__(self):
        self.max_length = len(self.words)

        if self.count < self.max_length:
            aa = self.count
            self.count = self.count + 1
            print("用于for循环测试")
            return self.words[aa]
        else:
            raise StopIteration


if __name__ == '__main__':
    s = Sentence("'go to bed   early and get up early',TRUNP said")
    # s = iter(s)
    while True:
        try:
            print(next(s))
        except StopIteration:
            break

结果为:

用于for循环测试
go
用于for循环测试
to
用于for循环测试
bed
用于for循环测试
early
用于for循环测试
and
用于for循环测试
get
用于for循环测试
up
用于for循环测试
early
用于for循环测试
TRUNP
用于for循环测试
said

通过结果我们可以看到,__iter__中的测试语句"哈哈"并没有输出,这说明,while循环迭代的时候,并没有调用这个函数,那就说明和它关系不大,甚至说,我们把__iter__这个函数注释了,对输出的结果也没什么影响,只是如果再使用for循环迭代的时候可能会有影响,还要再改回来。那while循环迭代时,是不是真的不需要__iter__函数呢?还真不是,我刚才说的不需要__iter__,更多的还是我们自定义的可迭代对象不需要,因为我们已经在类体中定义了__next__方法了,迭代只需要__next__方法去实现就够了,那如果是python内置的可迭代对象呢?(list,set,dict),先拿list类开刀:

a = [1,2,3]
while True:
    print(next(a))

结果:

Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/untitled/黑马升级考试.py", line 19, in <module>
    print(next(a))
TypeError: 'list' object is not an iterator

先说结果,直接报错,错误原因为:list不是一个迭代器,我们遍历一个可迭代对象时,一是先获取迭代器,二是调用__next__方法,然后逐个排出元素。显然list在这一块出了问题,我们点开list的源码,发现list类中有__iter__方法和__getitem__方法,没有__next__方法,这只能说明list是可迭代对象,不够成迭代器的条件,自然无法去迭代,同时也无法调用__next__,所以无法消耗可迭代的元素。下面的代码可能更详细点:

class A(list):
    def __iter__(self):
        print("哈哈")
        return super().__iter__()

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)


test_list = A([1,2,3])
while True:
    next(test_list)

错误和之前的一样,‘A’ object is not an iterator,无需解释。这又印证了之前的说法,__iter__没有被调用("哈哈"并没有输出到控制台),说明while循环的机制和for循环的机制不同。还一个例子是,for循环可以遍历一个列表,换到while就不行了。我们经常可以看到,如果真的想使用while去迭代列表,就必须把这个列表 套上iter()方法才行,这又是为什么呢?一个原因是调用__iter__方法,获取迭代器,如果实例对象没有实现_iter_,python会自动创建一个。还一个原因,给可迭代对象分配一个__next__方法,使它可以按规则取值。

class A(list):
    def __iter__(self):
        print("哈哈")
        return super().__iter__()
        # return [1,2,3]

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)

    def __next__(self):
        print("测试")

test_list = iter(A([1,2,3]))
while True:
    print(test_list.__next__())

结果:

哈哈
Traceback (most recent call last):
1
2
3
  File "C:/Users/Administrator/PycharmProjects/untitled/考试.py", line 20, in <module>
    print(test_list.__next__())
StopIteration

"哈哈"输出了,说明我们给可迭代对象套的iter()方法生效了,它调用了可迭代对象中的__iter__()方法,这个方法是我们自己定义的。如果我们把它注释,python仍然会创建一个类似的方法,只不过我们看不见,结果仍然和之前一样,只是不输出"哈哈"了,这说明如果获取不到__iter__,python会自动创建一个迭代器:

class A(list):
    # def __iter__(self):
    #     print("哈哈")
    #     return super().__iter__()
    #     # return [1,2,3]

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)

    def __next__(self):
        print("测试")
        
test_list = iter(A([1,2,3]))
while True:
    print(test_list.__next__())

结果:

class A(list):
    # def __iter__(self):
    #     print("哈哈")
    #     return super().__iter__()
    #     # return [1,2,3]

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)

    def __next__(self):
        print("测试")
        
test_list = iter(A([1,2,3]))
while True:
    print(test_list.__next__())

Traceback (most recent call last):
1
2
3
  File "C:/Users/Administrator/PycharmProjects/untitled/黑马升级考试.py", line 20, in <module>
    print(test_list.__next__())
StopIteration

可能还有人会疑惑,我们定义的__next__为什么没有生效? __next__方法中的print语句没有输出,刚才说过了,iter()这个"套筒"给可迭代对象赋予了新的__next__方法,这和__iter()一样,如果已经定义的__next__方法,就用已经定义的,没找到就自己创建一个。现在来解释下,__next__为什么没有生效,因为__iter__函数返回的是父类(list)的迭代器,list没有__next__方法,就用iter()"套筒"给它分配的那个__next__函数,所以不对调用我们自己定义的__next__函数。

class A(list):
    def __iter__(self):
        print("哈哈")
        return super().__iter__()
        # return [1,2,3]

    def __getitem__(self, item):
        print("itemm的值为:",item)
        return super().__getitem__(item)

    def __next__(self):
        print("测试")

test_list = iter(A([1,2,3]))
while True:
    print(test_list.__next__())

如果真想用我们自己定义的函数,就把__iter__函数中的返回值改为self,就会当前自定义可迭代对象的迭代器。

    def __iter__(self):
        print("哈哈")
        return self

__next__被调用,结果无限循环

哈哈
测试
None
测试
None
...

总结

  1. 迭代器不是惰性的,容器中的所有元素都存在于内存之中
  2. 生成器是一个惰性的迭代器,通过next()生产出一个元素放入内存之中,每次排除指挥占用一个元素的内存,内存占用并不会随着next()调用的次数而增长。

疑问

  1. 为什么在A的类里加了__next__方法,通过while循环加next()方法可以直接调用,而不加的话,就会报错,不是一个迭代器?
    据我观察,调用next()方法时,会直接出发可迭代对象中的__next__方法(直接跳过了__iter__()方法),而原生的list类中是没有next()方法的。

  2. 猜测:
    通过iter()方法或者通过for循环会调用可迭代对象的__iter__方法,然后这个方法会返回一个迭代器,这个迭代器不同于可迭代对象,它本身是附有自己的next方法和迭代机制的。所以,for循环后面的对象已经不是可迭代想本身了,而是迭代器(虽然看着还是可迭代对象)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值