美文网首页清风Python
你掌握迭代器和可迭代对象了么?不,你没有!

你掌握迭代器和可迭代对象了么?不,你没有!

作者: 清风Python | 来源:发表于2022-09-09 00:29 被阅读0次

    今天和大家聊聊Python的迭代器和可迭代对象。

    学前知识复习

    在学习生成器之前,我们需要先了解二者的关系:

    迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。

    可迭代对象

    在Python中,万物接对象!

    str, list, tuple, dict, set等等只要对象中包含了__iter__()方法的,都是可迭代对象。

    那么,如何查看对象是否包含__iter__()方法,又如何证明是否为可迭代对象呢?

    最简单的方式莫过于:

    from collections.abc import Iterable
    
    print('__iter__' in dir(10))
    # False
    print(isinstance(10, Iterable))
    # False
    
    print('__iter__' in dir('abc'))
    # True
    print(isinstance('abc', Iterable))
    # True
    

    因为数字不包含__iter__,所以数字不是可迭代对象。

    但这个说法绝对么?当然不是。

    还有一个例外:

    如果对象没有实现__iter__方法,但实现了__getitem__方法,Python会创建一个迭代器,尝试从索引0开始获取元素,若尝试失败,Python会抛出TypeError的异常。

    此时,可以使用 iter()内置方法 ,将对象转化为迭代器对象,迭代器一定是可迭代对象。

    但要注意,这种转换是暴力的强制方式,如果失败,虽然会抛出TypeError的异常,有时甚至产生不可估量的后果。

    上面这段话对于小白们太过晦涩,我来通过一个正向的例子为大家解释。

    from collections.abc import Iterable
    from collections.abc import Iterator
    
    class Demo:
        def __init__(self, word):
            self.word = word
    
        def __getitem__(self, index):
            return self.word[index]
    
    # 实例化demo_str对象
    demo_str = Demo("Hello")
    
    print('__iter__' in demo_str)
    # False
    print(isinstance(demo_str, Iterable))
    # False
    
    for j in demo_str:
        print(j)
    # H
    # e
    # l
    # l
    # o
    
    print(isinstance(iter(demo_str), Iterable))
    True
    print(isinstance(iter(demo_str), Iterator))
    True
    

    我们创建一个Demo类,并实例化出一个demo_str对象。由于demo_str本身不包含__iter__方法,所以不是一个可迭代对象。

    但由于我们有__getitem__方法,所以当我们for循环的时候,Python会尝试从索引0开始获取元素,最终遍历了初始化的Hello字符串,尝试成功了。

    此时,使用iter内置方法,可以将demo_str对象转化为一个迭代器对象,再去检测结果就为True了,并且这个迭代器是可以正常运行的。

    但为什么说iter是暴力强制方式呢?看看下面几个反例:

    d0 = iter(10)
    # TypeError: 'int' object is not iterable
    
    class Demo:
        def __init__(self, word):
            self.word = word
    
    d1 = iter(Demo(10))
    # TypeError: 'Demo' object is not iterable
    
    # 重点注意这个
    class Demo:
        def __init__(self, word):
            self.word = word
    
        def __getitem__(self, index):
            return self.word[index]
    
    d2 = iter(Demo(10))
    print(isinstance(d2, Iterable))
    # True
    print(isinstance(d2, Iterator))
    # True
    # 注意: 没有报错,返回了True!
    # 但对d2或者iter(d2)进行循环遍历时,发生了TypeError错误
    for i in iter(d1):
        print(i)
    # TypeError: 'int' object is not subscriptable
    

    前两种在通过iter方法转化时,就已经报错了,问题出现在源头,容易定位。

    但第三种方式就可怕了, iter方法强制将其成功转化为了可迭代对象,可是当我们循环调用可迭代对象时,才报了莫名其妙的TypeError错误,如果是真实开发场景,就会造成不可预估的问题。

    通过上面的例子,大家对可迭代对象的理解更深入了吧?

    迭代器

    讲完了可迭代对象,我们再来说说迭代器(对象),由于Python中万物接对象,所以迭代器也不例外,但出于简洁,一般大家都叫迭代器。

    迭代器是可以用于从集合中去除元素的对象。

    但迭代器和可迭代对象,到底有什么差别呢?迭代器在可迭代对象的基础上,实现了迭代器协议

    迭代器协议是指:

    对象需要提供next方法,从而它要么返回迭代中的下一项,要么就触发一个StopIteration异常,用以终止迭代 。

    说白了,就是迭代器是在可迭代对象的基础上,增加了一个__next__()的方法。

    自己实现迭代器

    让我们将刚才的Demo类进行改造,生成一个简单不可用的迭代器。

    class Demo:
        def __init__(self, word):
            self.word = word
            self.index = -1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            pass
    
    iter_demo = Demo('Hello')
    print(isinstance(iter_demo, Iterable))
    # True
    print(isinstance(iter_demo, Iterator))
    # True
    

    所为检测,不过就是条条框框的死规矩,那么我什么都不写,只是让对象具备__iter____next__方法,它就会被认为是一个迭代器。

    但,如何构造一个真实、可用的迭代器呢?其实很简单...

    class Demo:
        def __init__(self, word):
            self.word = word
            self.index = -1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.index < len(self.word) - 1:
                self.index += 1
                return self.word[self.index]
            else:
                raise StopIteration
    

    如上述代码,我们只需要对__next__方法,按照要求创建迭代子项,并在遍历完成后,抛出raise StopIteration的错误即可。

    如果我不抛出异常呢?

    那么for循环将无法得知当前迭代已结束,会造成 IndexError: xxx index out of range 的错误。

    通过iter方法实现迭代器

    当然,刚才提到的iter()其实就是给可迭代对象赋予__next__方法,同样可以生成迭代器。

    a = [1, 2, 3]
    print(isinstance(a, Iterable))
    # True
    print('__next__' in dir(a))
    # False
    print(isinstance(a, Iterator))
    # False
    a1 = iter(a)
    print('__next__' in dir(a1))
    # True
    print(isinstance(a1, Iterator))
    # True
    

    怎么样,通过讲解,大家是否对迭代器和可迭代对象有了深入的了解?其实在迭代器的上一层还有一个更为高级的生成器,它在我们日常的开发过程中,也有着举足轻重的作用,以及多种不同的玩法,我们放到下次再讲!

    欢迎关注我的公_众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。

    我的个人博客:https://qingfengpython.cn

    力扣解题合集:https://github.com/BreezePython/AlgorithmMarkdown

    相关文章

      网友评论

        本文标题:你掌握迭代器和可迭代对象了么?不,你没有!

        本文链接:https://www.haomeiwen.com/subject/mvdanrtx.html