Python中的【迭代器】和【生成器】

Python是一门被多层语法糖包装的编程语言,用户使用起来容易上手。但若不了解其底层机制,就无法精通其语言。最近研究了Python中的迭代器生成器

迭代器

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

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

  • 一类是集合数据类型,比如常见的 str、list、tuple、dict、set等
  • 另一类是生成器(generator),包括生成器和带 yield 的生成器函数

注意:可迭代对象不一定是迭代器,但迭代器一定是可迭代对象。他们是包含关系。

str、list、tuple、dict、set 都是可迭代对象(Iterable),但它们并不是迭代器(Iterator),我们可以通过迭代器中的 iter() 函数把这些可迭代对象(Iterable) 变成 迭代器(Iterator)

from collections import Iterable
from collections import Iterator

# int类型
print(isinstance(123, Iterable))  # 不是可迭代对象,输出:False

# str类型
print(isinstance("123", Iterable))  # 是可迭代对象,输出:True
print(isinstance("123", Iterator))  # 不是迭代器,输出:False
print(isinstance(iter("123"), Iterator))  # 变成了迭代器,输出:True

# list类型
print(isinstance([1, 2, 3], Iterable))  # 是可迭代对象,输出:True
print(isinstance([1, 2, 3], Iterator))  # 不是迭代器,输出:False
print(isinstance(iter([1, 2, 3]), Iterator))  # 变成了迭代器,输出:True

# tuple类型
print(isinstance((1, 2, 3), Iterable))  # 是可迭代对象,输出:True
print(isinstance((1, 2, 3), Iterator))  # 不是迭代器,输出:False
print(isinstance(iter((1, 2, 3)), Iterator))  # 变成了迭代器,输出:True

# dict类型
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable))  # 是可迭代对象,输出:True
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterator))  # 不是迭代器,输出:False
print(isinstance(iter({"a": 1, "b": 2, "c": 3}), Iterator))  # 变成了迭代器,输出:True

# set类型
print(isinstance({1, 2, 3}, Iterable))  # 是可迭代对象,输出:True
print(isinstance({1, 2, 3}, Iterator))  # 不是迭代器,输出:False
print(isinstance(iter({1, 2, 3}), Iterator))  # 变成了迭代器,输出: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 生成器来灵活控制读取,防止内存占用过大。

参考链接:https://www.jianshu.com/p/f84bda107146

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值