• Python中的迭代器和生成器


    今天我们来学习下Python中的迭代器和生成器。

    迭代和可迭代对象

    在学习迭代器之前,我们需要了解下迭代和可迭代对象的概念。

    迭代

    迭代是访问集合元素的一种方式,在Python中,迭代是通过 for ... in ... 语句来完成的。

    可迭代对象

    在Python中,可直接作用于 for循环 的对象都称为可迭代对象(Iterable),而可以作用于 for循环 的数据类型有以下两类:

    • 一类是集合数据类型,比如常见的 strlisttupledictset
    • 另一类是生成器(generator),包括生成器和带 yield 的生成器函数

    我们可以通过 isinstance() 来判断一个对象是否是可迭代对象:

    from collections import Iterable
    
    print(isinstance(123, Iterable))  # int类型,输出:False
    print(isinstance("123", Iterable))  # str类型,输出:True
    print(isinstance([1, 2, 3], Iterable))  # list类型,输出:True
    print(isinstance((1, 2, 3), Iterable))  # tuple类型,输出:True
    print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable))  # dict类型,输出:True
    print(isinstance({1, 2, 3}, Iterable))  # set类型,输出:True
    

    迭代器

    在Python中,可以被 next() 函数调用并不断返回下一个值的对象称为迭代器(Iterator),我们可以通过 isinstance() 来判断一个对象是否是迭代器:

    from collections import Iterator
    
    print(isinstance("123", Iterator))  # str类型,输出:False
    print(isinstance([1, 2, 3], Iterator))  # list类型,输出:False
    print(isinstance((1, 2, 3), Iterator))  # tuple类型,输出:False
    print(isinstance({"a": 1, "b": 2, "c": 3}, Iterator))  # dict类型,输出:False
    print(isinstance({1, 2, 3}, Iterator))  # set类型,输出:False
    

    从上面结果可以看到,虽然 strlisttupledictset 都是可迭代对象(Iterable),但它们并不是迭代器(Iterator),我们可以通过迭代器中的 iter() 函数把这些可迭代对象(Iterable)变成迭代器(Iterator)。

    from collections import Iterator
    
    print(isinstance(iter("123"), Iterator))  # str类型,输出:True
    print(isinstance(iter([1, 2, 3]), Iterator))  # list类型,输出:True
    print(isinstance(iter((1, 2, 3)), Iterator))  # tuple类型,输出:True
    print(isinstance(iter({"a": 1, "b": 2, "c": 3}), Iterator))  # dict类型,输出:True
    print(isinstance(iter({1, 2, 3}), Iterator))  # set类型,输出:True
    

    迭代器中有2个基本方法:iter()next(),使用iter()创建一个迭代器后,就可以通过 next() 获取迭代器的下一个值,如果通过 next() 不断调用并返回下一个值,那么等到最后没有下一个值了,就会抛出异常:StopIteration

    a = [1, 2, 3]
    iter_a= iter(a)  # 创建迭代器对象
    
    print(next(iter_a))  # 输出:1
    print(next(iter_a))  # 输出:2
    print(next(iter_a))  # 输出:3
    print(next(iter_a))  # iter_a没有下一个值了,报错:StopIteration
    

    在Python中,我们经常使用的 for循环 本质上就是通过不断调用 next() 实现的。

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

    从上面结果可以看到,我们使用 for循环 并不会抛出异常,这是因为其使用了 try ... except ... 语句,当遇到出现 StopIteration 就退出循环,其可看作等价于下面的代码实现:

    a = [1, 2, 3]
    iter_a = iter(a)  # 创建迭代器对象
    while True:
        try:
            print(next(iter_a))  # 输出迭代器的下一个值
        except StopIteration:  # 遇到 StopIteration 就退出循环
            break
    

    我们还可以用迭代器去实现斐波那契数列:

    class Fib:
    
        def __init__(self, n):
            self.n = n
            self.pre = 0
            self.cur = 1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.n > 0:
                value = self.cur
                self.pre, self.cur = self.cur, self.pre + self.cur
                self.n -= 1
                return value
            else:
                raise StopIteration
    
    
    fib = Fib(11)
    for i in fib:
        print(i, end=" ")  # 输出:1 1 2 3 5 8 13 21 34 55 89 
    

    生成器

    在Python中,生成器(generator)也是用在迭代操作中,其本质上可以理解为一个特殊的迭代器,生成器具有和迭代器一样的特性,但它们在实现方式上不一样。我们可以通过两种不同的方式创建生成器:生成器表达式、生成器函数。

    • 生成器表达式

    生成器表达式和列表推导式差不多,我们只需要把列表推导式的 [] 改为 () ,这样就是一个生成器表达式了。需要注意的是,列表推导式返回的是一个列表对象,而生成器表达式返回的是一个生成器对象,因此我们可以通过生成器表达式来创建一个生成器。

    a = [i for i in range(5)]
    print(type(a))  # <class 'list'>
    print(a)  # [0, 1, 2, 3, 4]
    
    b = (i for i in range(5))
    print(type(b))  # <class 'generator'>
    print(b)  # <generator object <genexpr> at 0x0000022A6719E7C8>
    

    从上面可以看到,我们可以直接打印出列表对象的所有元素,但无法直接打印出生成器的所有元素。如果需要打印输出元素,我们可以使用 next() 函数,或者通过 for 循环来完成。

    b = (i for i in range(5))
    for i in b:
        print(i, end=" ")  # 输出:0 1 2 3 4 
    
    • 生成器函数

    在Python中,普通函数一般通过 return 来返回一个值,当我们使用关键字 yield 来返回值,那么这个带有 yield 的函数就变成了生成器函数。

    def demo1(n):
        return n
    
    
    def demo2(n):
        yield n
    
    
    a = demo1(100)
    print(type(a))  # <class 'int'>
    
    b = demo2(100)
    print(type(b))  # <class 'generator'>
    

    生成器函数和普通函数在执行流程上是有点区别的。对于普通函数,按顺序执行时遇到 return 或最后一行函数语句就会返回;对于有 yield的生成器函数,每次调用 next() 方法遇到 yield 语句才返回,如果再次调用 next() 方法,那么就会从上次返回的 yield 语句位置继续执行,请看下面的例子:

    def demo3():
        print("aaa")
        yield 1
        print("bbb")
        yield 2
        print("ccc")
        yield 3
    
    
    g = demo3()
    next(g)  # 输出:aaa
    next(g)  # 输出:bbb
    next(g)  # 输出:ccc
    next(g)  # 没有下一个值了,报错:StopIteration
    

    生成器是一个特殊的迭代器,它使用起来更简单,代码更简洁。我们可以用生成器去实现斐波那契数列:

    def fib(n):
        pre, cur = 0, 1
        while n > 0:
            yield cur
            pre, cur = cur, pre + cur
            n -= 1
    
    
    f = fib(11)
    for i in f:
        print(i, end=" ")  # 输出:1 1 2 3 5 8 13 21 34 55 89
    

    最后,生成器还可以用于处理海量数据的场景,比如读取大文件时,如果直接一次性读取可能会导致内存溢出,这个时候我们就可以借助 yield 生成器来灵活控制读取,防止内存占用过大。

  • 相关阅读:
    DAY-4 Linux基础及常用命令(1)
    DAY-3 计算机基础之网络
    DAY-2 计算机基础之操作系统
    DAY-1 计算机基础
    梅花作品欣赏
    简洁大气网址(国外)跟设计大学的案例很像
    animate css3 应用的借鉴,一个同事写的JS
    漂亮的素材
    几个不错的素材站
    正式开始我的技术生涯
  • 原文地址:https://www.cnblogs.com/wintest/p/16270953.html
Copyright © 2020-2023  润新知