美文网首页python技巧
迭代器和生成器

迭代器和生成器

作者: 陆_志东 | 来源:发表于2018-09-02 13:07 被阅读0次

    首先说说python容器:

    容器是一种把Python对象存放在一起的数据结构,对于容器来说,我们可以逐个的迭代获取里面的元素,也可以使用in or not in 判断容器中是否有我们想要的对象.在Python中常见的容器对象有:

    • list []
    • dict {}
    • set {}
    • tuple ()
    • 生成器
    list1 = [1,2,3]
    print(4 in list1)
    >>False
    print(1 in list1)
    >>True
    for obj in list1:
        print(obj)
    >>1
    >>2
    >>3
    

    如何判断一个对象是否可迭代?

    使用isinstance判断对象是不是Iterable类的实例
    from collections import Iterable
    print(isinstance([],Iterable))
    >>True
    print(isinstance(1,Iterable))
    >>False
    

    如何判断一个对象是否是迭代器?

    from collections import Iterator
    print(isinstance([], Iterator))
    >>False
    print(isinstance(iter([]), Iterator))
    >>True
    print(isinstance(iter("abc"), Iterator))
    >>True
    

    我们如何实现一个可迭代对象呢?

    我们需要知道在Python中万物皆对象,想要一个对象是可迭代对象,只需要在对象的创建类里面添加__iter__魔法方法即可,__iter__魔法方法作用就是让一个对象可迭代
    比如我们先新建一个不可迭代的类MyIter

    class MyIter:
        def __init__(self):
            pass
    myiter1 = MyIter()  # 实例化出来的对象是不可迭代的,对不可迭代的对象遍历,会引发异常
    for data in myiter1:
        print(data)
    >>TypeError: 'MyIter' object is not iterable
    

    下面通过__iter__魔法方法让我们的不可迭代类MyIter变为可迭代的类

    class MyIter:
        def __init__(self):
            pass
    
        def __iter__(self):
            return # 这里我们直接return,先什么都不做
    
    myiter1 = MyIter()
    for data in myiter1:  # 一样会触发异常,这是因为我们并没有在__iter__里面返回一个迭代器或者生成器
        print(data)
    >>TypeError: iter() returned non-iterator of type 'NoneType'
    # 不过没关系,他已经是一个可迭代对象了,请看
    from collections import Iterable
    print(isinstance(myiter1,Iterable))
    >>True   # 结果已经显示为可迭代了
    

    接下来我们解决for循环TypeError的问题,在iter魔法方法里面返回一个迭代器或者一个生成器,生成器会在迭代器下面进行讲解,这里我们先返回一个迭代器

    class MyIter:
        def __init__(self):
            pass
    
        def __iter__(self):
            return iter([1,2,3])  # 注意是迭代器,不是可迭代对象
    
    myiter1 = MyIter()
    from collections import Iterable
    print(isinstance(myiter1,Iterable))
    >>True
    for data in myiter1:
        print(data)
    >>1
    >>2
    >>3
    注意:迭代器和生成器都是一次性的,不能回滚,如果想要再次使用,需要重新实例化
    下面再次使用myiter1,是可以继续得到1,2,3的
    这是因为我们的写法原因,我们这里的写法每次都会重新return一个迭代器
    for data in myiter1:
        print(data)
    >>1
    >>2
    >>3
    
    下面修改我们的写法,来演示一次性效果
    class MyIter:
        def __init__(self):
            self.can_iter = iter([1,2,3])
    
        def __iter__(self):
            return self.can_iter
    
    myiter1 = MyIter()
    for data in myiter1:
        print(data)
    print("--------------")
    for data in myiter1:
        print(data)
    输出结果如下
    >>1
    >>2
    >>3
    >>--------------
    可以看到第二个for循环什么都没有输出,如果我们想要再次使用就需要再次实例化
    myiter2 = MyIter()
    for data in myiter2:
        print(data)
    >>1
    >>2
    >>3
    

    可以看到上面的代码并没有继续出现TypeError的问题,所以我们可以看出__iter__魔法方法的实际作用就是为了返回一个迭代器或者生成器的.
    对于迭代器和生成器我们也可以使用next函数去获取迭代器or生成器里面的内容,不过当内容获取完之后会触发StopIteration异常

    iter1 = iter([0,1,2])
    print(next(iter1))
    print(next(iter1))
    print(next(iter1))
    print(next(iter1))
    >>0
    >>1
    >>2
    Traceback (most recent call last):
      StopIteration
    但是需要注意的是如果使用for循环并不会看到StopIteration这个异常
    for data in iter1:
        print(data)
    程序执行后什么都不会输出,且看不到StopIteration异常.
    什么都不输出是因为迭代器是一次性的,不能回退,所以没有内容可输出
    而为什么没有StopIteration异常,这是因为for循环会捕获这个异常,当for循环捕捉到StopIteration,就会停止迭代
    

    由上面的代码我们可以总结出for...in 循环的本质

    for 循环对可迭代对象进行迭代的过程:
    1.首先调用__iter__魔法方法,获取到迭代器或生成器
    2.使用next函数获取迭代器和生成器的下一个值
    3.当没有值可获取之后,触发StopIteration异常反馈给for循环
    4.for循环捕获StopIteration异常,并停止循环
    

    其实问题说到这就会发现上面__iter__方法返回的迭代器,是用iter函数创建出来的,我们能不能自己定义一个迭代器供iter方法返回呢?答案是可以,请继续看这篇文章

    如何自定义一个迭代器

    自定义一个迭代器需要类的构造中要有__next__方法.这个方法的作用就是能够记录我们已经迭代到的位置,当没有元素可以迭代之后触发StopIteration异常.
    下面我们的做法是给自己的可迭代类添加一个__next__方法,变成一个迭代器,同时通过__iter__方法返回我们实例本身

    就以生成斐波那契数列为例:
    import sys
    
    
    class MyIter:
        def __init__(self, max_size=None):
            self.prev_num = 0
            self.current_num = 1
            self.index = 0
            self.max_size = max_size
            if max_size is None:
                self.max_size = sys.maxsize
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.index >= self.max_size:
                raise StopIteration
            self.index += 1
            return_num =self.prev_num
            self.prev_num = self.current_num
            self.current_num += return_num
            return return_num
    
    
    myiter1 = MyIter()  # 实例化一个不限制大小的迭代器
    for i in range(10):
        print(next(myiter1), end=" ")
    print()
    >>0 1 1 2 3 5 8 13 21 34 
    myiter2 = MyIter(10)  # 限制大小
    for j in myiter2:
        print(j, end=" ")
    print()
    >>0 1 1 2 3 5 8 13 21 34 
    

    由上面的例子得出我们可以使用__next__方法创造出各种各样算法的迭代器

    使用迭代器的好处

    如果我们需要一批数据,又知道数据产生的方法,那么我们就可以使用迭代器来生产数据,而不是事先生成放在内存中
    注意在Python中并不是只有for循环可以迭代,listtuple也可以迭代
    比如你使用高级函数mapfilter,返回的是一个迭代器,直接print是看不到内容的,那么有两个选择,for循环遍历,使用list迭代转换为列表打印这个列表

    生成器

    生成器其实就是特殊的迭代器

    创建生成器的方法一:改造列表生成式

    list1 = [x for x in range(5)]
    print(list1)
    >>[0, 1, 2, 3, 4]
    generate1 = (x for x in range(5))
    print(generate1)
    >><generator object <genexpr> at 0x0000000001EF5938>
    print(list(generate1))  # list迭代转换
    >>[0, 1, 2, 3, 4]
    generate2 = (x for x in range(5))
    print(next(generate2))
    >>0
    print(next(generate2))
    >>1
    print(next(generate2))
    >>2
    print(next(generate2))
    >>3
    print(next(generate2))
    >>4
    print(next(generate2))
    Traceback (most recent call last):
      StopIteration
    

    方式二:改造函数(yield)

    同样以斐波那契数列为例
    import sys
    
    
    def generate_feibo(max_size=None):
        if max_size is None:
            max_size = sys.maxsize
        prev_num = 0
        current_num = 1
        index = 0
        while index < max_size:
            return_num =prev_num
            yield return_num
            index += 1  # 这一步放yield前面和后面执行效果都一样
            prev_num = current_num
            current_num += return_num
        return
    
    
    myiter1 = generate_feibo()  # 实例化一个不限制大小的生成器
    for i in range(3):
        print(next(myiter1), end=" ")
    >>0 1 1 
    print()
    myiter2 = generate_feibo(3)
    for data in myiter2:
        print(data, end=" ")
    >>0 1 1 
    

    使用yield之后,就不需要手动抛出StopIteration异常了,当生成器没有使用yield返回数据,而是使用return结束函数的时候会自动抛出StopIteration异常

    使用生成器的实例二:读取一个超大文件

    def read_file():
        with open("./oid.txt","r",encoding="utf-8",newline="\n") as f:
            while True:
                data = f.readline().strip()
                if data is None or not data:
                    break
                yield data
        return
    

    相关文章

      网友评论

        本文标题:迭代器和生成器

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