【Python】【进阶】迭代器和生成器

迭代器

可迭代(iterable)对象:实现了方法 __iter__ 的对象是可迭代的。

比如,写一个类,不实现__iter__方法,用for循环尝试进行迭代,会提示不是可迭代对象。

判断是否为可迭代对象或迭代器对象,均返回False

from collections.abc import Iterator, Iterable, Generator


class Fib(object):
    def __init__(self):
        print('__init__被调用')
        self.a = 0
        self.b = 1

    # def __next__(self):
    #     print('__next__被调用')
    #     self.a, self.b = self.b, self.a + self.b
    #     if self.a > 50:
    #         raise StopIteration
    #     return self.a
    #
    # def __iter__(self):
    #     print('__iter__被调用')
    #     return self


fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
    print(i)


执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象:  False
被测对象是否为迭代器对象:  False
Traceback (most recent call last):
  File "E:/Pycharm-Project/test.py", line 25, in <module>
    for i in fib:
TypeError: 'Fib' object is not iterable

Process finished with exit code 1

再加上__iter__方法,就可以用for循环。才疏学浅,暂不清楚怎么创建一个可以for循环取值又不用__next__的可迭代对象,只能看着它报错。

检查类型,已经是可迭代对象,但不是迭代器对象

from collections.abc import Iterator, Iterable, Generator


class Fib(object):
    def __init__(self):
        print('__init__被调用')
        self.a = 0
        self.b = 1

    # def __next__(self):
    #     print('__next__被调用')
    #     self.a, self.b = self.b, self.a + self.b
    #     if self.a > 50:
    #         raise StopIteration
    #     return self.a

    def __iter__(self):
        print('__iter__被调用')
        return self


fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
    print(i)


执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象:  True
被测对象是否为迭代器对象:  False
__iter__被调用
Traceback (most recent call last):
  File "E:/Pycharm-Project/test.py", line 25, in <module>
    for i in fib:
TypeError: iter() returned non-iterator of type 'Fib'

Process finished with exit code 1

迭代器(Iterator)对象:

1、实现了__next__方法

2、实现了__iter__方法,本方法只能return self

from collections.abc import Iterator, Iterable, Generator


class Fib(object):
    def __init__(self):
        print('__init__被调用')
        self.a = 0
        self.b = 1

    def __next__(self):
        print('__next__被调用')
        self.a, self.b = self.b, self.a + self.b
        if self.a > 50:
            print('迭代中止,利用StopIteration退出')
            raise StopIteration
        return self.a

    def __iter__(self):
        print('__iter__被调用')
        return self


fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
for i in fib:
    print(i)


执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象:  True
被测对象是否为迭代器对象:  True
__iter__被调用
__next__被调用
1
__next__被调用
1
__next__被调用
2
__next__被调用
3
__next__被调用
5
__next__被调用
8
__next__被调用
13
__next__被调用
21
__next__被调用
34
__next__被调用
迭代中止,利用StopIteration退出

Process finished with exit code 0

由执行结果可以看出原理:

fib = Fib(),首先调用__init__初始化一个对象;
for循环会调用fib()__iter__,使其为一个迭代器对象;
循环调用__next__,遇到StopIteration错误后结束循环。

列表字典等可迭代对象

listsetdicttuplestr都是可迭代对象
在这里插入图片描述

但是他们都不是迭代器对象,因为没有实现__next__方法
在这里插入图片描述

如果用next()操作一个列表会提示不是迭代器对象

lista = ['a', 'b', 'c', 'd']
print(next(lista))

Traceback (most recent call last):
  File "G:/Study/Code_github/Studyfor/test.py", line 2, in <module>
    print(next(lista))
TypeError: 'list' object is not an iterator

迭代器对象是可以用next()操作的,因为next()本质上是调用了迭代器对象的__next__方法

from collections.abc import Iterator, Iterable, Generator


class Fib(object):
    def __init__(self):
        print('__init__被调用')
        self.a = 0
        self.b = 1

    def __next__(self):
        print('__next__被调用')
        self.a, self.b = self.b, self.a + self.b
        if self.a > 50:
            print('迭代中止,利用StopIteration退出')
            raise StopIteration
        return self.a

    def __iter__(self):
        print('__iter__被调用')
        return self


fib = Fib()
print('被测对象是否为可迭代对象: ', isinstance(fib, Iterable))
print('被测对象是否为迭代器对象: ', isinstance(fib, Iterator))
print(next(fib))
print(next(fib))
print(fib.__next__())


执行结果:
"D:\Program Files\Python37\python.exe" E:/Pycharm-Project/test.py
__init__被调用
被测对象是否为可迭代对象:  True
被测对象是否为迭代器对象:  True
__next__被调用
1
__next__被调用
1
__next__被调用
2

Process finished with exit code 0

优缺点

优点

  • 取值不依赖索引

  • 节省内存

    比如需要一个十亿位的斐波拉契数列,如果使用列表,这个列表暂用的内存会很大,但是使用迭代器,每次只需要一位的内存占用即可。

缺点

  • 只能往后取,不能往前取

    迭代器只有__next__,元素取完之后,再也无法找回

  • 无法预测长度

    迭代器是不知道前面有多少,不知道后面有多少,所以无法预测长度。

生成器

生成器是一种特殊的迭代器,生成器有两种,生成器函数和生成器表达式

生成器函数

生成器函数的创建比较简单,用yield替代return进行返回就是一个生成器函数。

from collections.abc import Iterator, Iterable, Generator


def my_func():
    print('before yield 1')
    yield 1
    print('after yield 1')


    print('before yield 2')
    yield 2
    print('after yield 2')

    print('before yield 3')
    yield 3
    print('after yield 3')

    yield 4


obj = my_func()
print(obj)
print(f'被测对象是否为生成器对象: {isinstance(obj, Generator)}. \n')

a1 = next(obj)
print('main run %s \n' % a1)

a2 = next(obj)
print('main run %s \n' % a2)

a3 = next(obj)
print('main run %s \n' % a3)


执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<generator object my_func at 0x0000021E1919EC48>
被测对象是否为生成器对象: True. 

before yield 1
main run 1 

after yield 1
before yield 2
main run 2 

after yield 2
before yield 3
main run 3 


Process finished with exit code 0

从上面的例子来看。

带有yield的函数,返回的obj是一个生成器(Generator)对象。
a1的函数执行和主流程print部分,函数里面执行到yield就结束了。
a2的函数执行部分,才继续执行a1终止位置的yield后面的部分,直到第二个yield结束。

斐波拉契数列表示

from collections.abc import Iterator, Iterable, Generator


def fib():
    a = 0
    b = 1
    while True:
        a, b = b, a+b
        if b > 30:
            raise StopIteration
        yield b


obj = fib()
print(obj)  # <generator object fib at 0x000001C0AD91EC48>
print(f'被测对象是否为生成器对象: {isinstance(obj, Generator)}. \n')  # 被测对象是否为生成器对象: True. 

a1 = next(obj)
print('main run %s \n' % a1)  # main run 1 

a2 = next(obj)
print('main run %s \n' % a2)  # main run 2

a3 = next(obj)
print('main run %s \n' % a3)  # main run 3

send()方法

生成器函数除了恢复执行的next()方法,还有一个往里传值并恢复执行的send()方法。

先看一下官方文档:

generator.send(value)

恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。 
send()方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。 
当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。

然后再看实际的例子:

def my_func():
    print('未到"yield"语句时,没有传值')
    
    a1 = yield 1
    print(f'my_func收到传入值a1: "{a1}"')

    a2 = yield 2
    print(f'my_func收到传入值a2: "{a2}"')

    yield 3


obj = my_func()
b0 = obj.send(None)
print('main run %s \n' % b0)

b1 = obj.send('one')
print('main run %s \n' % b1)

b2 = obj.send('two')
print('main run %s \n' % b2)


执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
未到"yield"语句时,没有传值
main run 1 

my_func收到传入值a1: "one"
main run 2 

my_func收到传入值a2: "two"
main run 3 


Process finished with exit code 0

结合官方文档,看上面的例子。

b0所在的send部分用来启动生成器,以None作为参数。同时b0处的send也有着恢复执行的作用,执行到yield 1
b1所在的send部分,传入一个one,赋值给a1,再执行print语句,到yield 2结束,返回后给b1

从这个例子来理解yield对应的赋值,a1 = yield 1其实应该分为两部分a1 = yieldyield 1。实际代码执行顺序是:

    print('未到"yield"语句时,没有传值')

    yield 1  # b0的send()在这里结束
    a1 = yield  # b1的send()在这里开始
    print(f'my_func收到传入值a1: "{a1}"')

但是,这只是为了更好的理解a1 = yield 1的执行顺序,同时理解为什么启动生成器的send()需要传入一个None,因为启动生成器的send()传进去的值没有变量可以接收。

实际使用不能拆开写,因为按照生成器遇到yield必停的原则,拆开得停止两次了。

可以看下如果启动生成器的send()传入不是None的报错信息:

Traceback (most recent call last):
  File "G:/Study/Code_github/Studyfor/test.py", line 17, in <module>
    b1 = obj.send('one')
TypeError: can't send non-None value to a just-started generator

生成器表达式

先看比较熟悉的列表的表达式:list_expr = [i for i in range(10)]

生成器表达式就是换一下最外层的括号:generator_expr = (i for i in range(10))

但是,生成器表达式只能是表达式,不能是具体的元素:tuple_expr = (1, 2, 3)

其实也好理解,生成器是一种迭代器,是不知道前,也不知道后的。如果有了具体的元素,那就什么都知道了。

list_expr = [i for i in range(10)]
generator_expr = (i for i in range(10))
tuple_expr = (1, 2, 3)
tuple_expr2 = tuple((i for i in range(10)))

print(type(list_expr))
print(type(generator_expr))
print(type(tuple_expr))
print(type(tuple_expr2))


执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<class 'list'>
<class 'generator'>
<class 'tuple'>
<class 'tuple'>

Process finished with exit code 0

具体的元素,那就什么都知道了。

```Python
list_expr = [i for i in range(10)]
generator_expr = (i for i in range(10))
tuple_expr = (1, 2, 3)
tuple_expr2 = tuple((i for i in range(10)))

print(type(list_expr))
print(type(generator_expr))
print(type(tuple_expr))
print(type(tuple_expr2))


执行结果:
"D:\Program Files\Python37\python.exe" G:/Study/Code_github/Studyfor/test.py
<class 'list'>
<class 'generator'>
<class 'tuple'>
<class 'tuple'>

Process finished with exit code 0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值