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))  # 
print(a)  # [0, 1, 2, 3, 4]

b = (i for i in range(5))
print(type(b))  # 
print(b)  #  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))  # 

b = demo2(100)
print(type(b))  # 

生成器函数和普通函数在执行流程上是有点区别的。对于普通函数,按顺序执行时遇到 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 生成器来灵活控制读取,防止内存占用过大。

你可能感兴趣的:(Python中的迭代器和生成器)