0
点赞
收藏
分享

微信扫一扫

python语言基础(十)迭代器、生成器

Gaaidou 2022-03-12 阅读 106

迭代器

迭代器介绍

迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每次重复执行一行代码就是迭代的过程,每一次重复都是基于上一次结果而来,迭代提供了一种通用的 不依赖索引 的迭代取值方式。
之前学习列表的时候,可以通过下标、循环遍历来进行取值,for循环是最典型的迭代器。
for i in range(10),是利用for循环把0-9中的数逐个取出来,操作是不可逆的,一直往下执行,这个过程就是迭代的过程。

可迭代对象

可以用 for 循环遍历的对象都是可迭代对象。
1.str,list,tuple,dict,set 等都是可迭代对象。dict可以先获取到items,然后通过循环取出对应的key和value值。int,float是不可迭代对象。
2. generator (生成器 和 yield 的生成器函数) 也是可迭代对象。

判断是否可迭代

1.查看是否有内置的__iter__()方法来验证是否为可迭代对象。如在pycham中输入str(),CTRL+鼠标左键点击str进去说明文档,可以看到里面有def__iter__()的方法。
2.通过 isinstance(obj,Iterable)来判断是否是可迭代对象。

from collections import Iterable
print(isinstance('abc', Iterable))
print(isinstance({1, 5, 7}, Iterable))
print(isinstance(12, Iterable))
print(isinstance(12.6, Iterable))
print(isinstance([12.6,'2a',[1,2]], Iterable))

运行的结果是:

True
True
False
False
True

迭代器

1.有内置的__iter__()方法的对象,执行迭代器的__iter__()方法得到的依然是迭代器本身
2.有内置的__next__()方法的对象,执行该方法可以不依赖索引取值。
可迭代对象不一定是迭代器,可以通过两种方法判断。
1.看是否含有__iter__()和__next__()方法。比如列表是可迭代对象,但不是迭代器,它没有next方法。
2.看是否属于Iterator

from collections import Iterator
print(isinstance([12.6,'2a',[1,2]], Iterator))   # False

iter()

可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator。那我们可以通过 iter() 方法将可迭代的对象,转为迭代器。

li = [1,2,3,5]
print(type(li))     #  <class 'list'>
# lis = li.__iter__()
lis = iter(li)
print(type(lis))   #  <class 'list_iterator'>

迭代器对象如何取值?
可以通过 next() 或者 next()来取值,但是通常使用next()取值。

li = [1, 2, 3, 5]
lis = iter(li)
# for i in lis:
#     print(i)  # 可以用for循环,依次取出值
# print(lis[0])   # 不能通过下标取值
# print(lis.__next__())   # 取出 1
# print(lis.__next__())   # 取出 2
# print(lis.__next__())   # 取出 3
# print(lis.__next__())   # 取出 4
# print(lis.__next__())   # 超出长度,报错:StopIteration

print(next(lis))
print(next(lis))
print(next(lis))
print(next(lis))
print(next(lis))

注意:

  1. 迭代器不可以通过下标取值,而是使用 next() 或者 next() 。但是只要超出范围则直接报错 StopIteration 。
  2. next() 只能顺延调用,不能往前。

迭代器不一次性的取出所有数据,可以逐个取出元素,用多少取多少,减少内存的消耗。循环(for、while)的本质就是迭代器。

可迭代对象与迭代器区别

  1. 可用于 for 循环的都是可迭代对象
  2. 作用于 next() 都是迭代器对象
  3. list、dict、str 等都是可迭代的但不是迭代器,因为 next() 函数无法调用它们。可以通过 iter() 函数将它们转为迭代器
  4. python 的 for 循环本质就是通过不断调用 next() 函数实现

生成器

生成器定义

在Python 中,一边循环一边计算的机制,称为生成器:generator
在这里插入图片描述

上图列表中存放无限多的元素,内存占用也是很大的。如果需求是只访问前面5个元素,那后面元素所占用的内存就会被浪费掉,如果列表过大,就会浪费内存。
那么生成器就是在循环的过程中根据算法不断推算出后续的元素,这样就不用创建整个完整的列表,从而节省大量的空间。
在这里插入图片描述
从元素0开始,用哪个元素就取出哪个,依次用多少元素就取出多少,不用的话就停在那,等需要的时候,再进行依次取出。
相当于一个人要吃100个馒头,而厨师一下子蒸100个一起给他,既浪费时间,也占用资源,吃不完的话就会造成浪费;如果说厨师上一个,他就吃一个的话,既节省时间,也可以在吃饱的时候随时喊停。

如何创建生成器

生成器表达式

生成器表达式来源于 迭代列表解析 (列表推导)的组合,生成器和列表解析(列表表达式)非常类似,但是它使用 () 而不是 []
在这里插入图片描述

g = (i for i in range(5))
# print(g)        # generator 生成器
# print(g[0])     # 生成器不可通过下标取值
print(next(g))     # 依次取出元素值,顺延的状态,不可跳跃式取值
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))      # 生成器也是可迭代的对象,超出取值范围会报错 StopIteration停止迭代

# 也可以用for循环控制打印的内容
for i in g:
    if i <3:
        print(i)

生成器函数( yield )

实现:
生成一个自定义长度的列表
需求:

  1. 定义函数 yieldtest
  2. 通过函数参数指定列表长
def yield_test(number):
    n = 0
    li = []
    while n < number:
        li.append(n)
        n += 1
    print(li)

yield_test(10)

当传入的实参很大的时候,比如200000,会生成很多的数,浪费内存空间。

当一个函数中包含 yield 关键字,那么这个函数就不再是一个普通的函数,而是一个 generator 。
调用函数就是创建了一个生成器对象。其工作原理就是通过重复调用 next() 或者 next() 方法,直到捕获一个异常。

# 在函数中加入yield,该函数就变为生成器函数
def yield_test(number):
    n = 0
    # li = []
    while n < number:
        yield n
        # li.append(n)
        n += 1
    # print(li)

res = yield_test(10)
# print(res)    # 生成器对象,<generator object yield_test at 0x000001E4E91B1CA8>
print(next(res))    # 0
print(next(res))    # 1
print(next(res))    # 2
print(next(res))    # 3

注意:
yield 返回一个值,并且记住这个返回值的位置,下次遇到 next() 调用时,代码从yield 的下一条语句开始执行。与 return 的差别是,return 也是返回一个值,但是直接结束函数。

# 斐波那契数列:除了第一个和第二个数以外,任何一个数都是前两个数的和
def createNums():
    print('----func start----')
    a, b = 0, 1
    for i in range(5):
        print('---1---')
        yield b
        print('---2---')
        a, b = b, a+b
        print('---3---')
    print('----func end----')

g = createNums()
# print(g)   # 生成器对象,<generator object createNums at 0x000001ECF8F81CA8>
'''程序从上至下执行,函数调用createNums(),打印func start,打印---1---,到达yield,next相当于触发了yield,
将b返回给了next(g) '''
print(next(g))      # ----func start----,---1---,1
print(next(g))      # 遇到第二个next(g),会接着上一次yield下面的代码继续执行,---2------3------1---1
print(next(g))      # ---2------3------1---2,改变的是b的值

小结:一旦有yield就会将生成的值进行返回到第一个next()处,继续出现next()时不会从头开始运行代码,而是接着yield下面的代码继续执行,直到又遇到yield,将后面的值返回到第二个next()处。
也可以通过for循环全部取出

# 斐波那契数列:除了第一个和第二个数以外,任何一个数都是前两个数的和
def createNums():
    print('----func start----')
    a, b = 0, 1
    for i in range(5):
        print('---1---')
        yield b
        print('---2---')
        a, b = b, a+b
        print('---3---')
    print('----func end----')

g = createNums()
for i in g:
    print(i)

yield与return的区别

return:函数的返回值,当函数代码执行到return时,就退出了函数,后面的代码都不会再执行。
yield:是将函数变为生成器的关键字,将值返回到next(),再遇到下一个next()时,会接着上一次执行的代码继续执行。

send()

send() 和 next() 一样,都能让生成器继续往下走一步(遇到 yield 返回),但 send() 能传一个值,这个值作为 yield 表达式整体的结果。

def test():
    a1 = yield 'hell0'
    yield a1

res = test()
print(next(res))    # hell0
print(res.send('world'))    # world

程序从上至下执行,调用test(),在 a1 = yield 'hell0’的赋值运算中,先执行右边的yield ‘hello’,停到了此处,还没有执行赋值给a1的操作。所以第一个print返回的是hello。执行第二个print语句,send()跟next()一样,让代码继续执行,此时相当于a1 = ‘world’,到第二个yield处,yield a1,将a1返回到send()处。再有一个send() 的话就会报错,因为代码中只有两个yield,已经返回完了,且代码没有循环,那么再使用yield或者send时就超出了,会报错。(StopIteration)

res = test()
print(res.send('world'))
print(res.send(None))	# hello ,相当于next()

在上面的代码中,如果直接用send()就会报错,因为send不能使用非空的值作为启动生成器的对象。可以使用’None’,相当于next()。
在这里插入图片描述

迭代器与生成器总结

  1. 生成器能做到迭代器能做的所有事
  2. 因为生成器自动创建了 iter() 和 next() 方法,生成器显得简洁,而且高效。
举报

相关推荐

0 条评论