8、迭代器与生成器


1、什么是迭代

使用for循环遍历取值的过程叫做迭代

  • 迭代列表
list1 = [11, 22, 33, 44]
for i in list1:
    print(i)
# 11
# 22
# 33
# 44
  • 迭代元组
tuple1 = (11, 22, 33, 44)
for i in tuple1:
    print(i)
# 11
# 22
# 33
# 44
  • 迭代字符串
str1 = "abcde"
for i in str1:
    print(i)
# a
# b
# c
# d
# e
  • 迭代字典
dic1 = {'name': 'zht', 'age': 25, 'school': 'zhbit'}
  • -默认迭代的是 键
for i in dic1:
    print(i)
# name
# age
# school
    • 迭代字典的值
for i in dic1.values():
    print(i)

# zht
# 25
# zhbit
    • 迭代字典的键和值
for i1, i2 in dic1.items():
    print(i1, i2)
# name zht
# age 25
# school zhbit
  • 迭代集合
s = {'apple', 'banana', 'pear', 'orange'}
for i in s:
    print(i)
# apple
# pear
# orange
# banana

2、可迭代对象(Iterable)

可以使用for循环遍历取值的对象(迭代是过程,可迭代对象是对象),就是可迭代对象。如上面的例子,列表、元组、字典、集合、字符串都是可迭代对象

可以使用 isinstance() 函数判断该对象是否是可迭代对象

In:     from collections import Iterable # 引入模块

In:     isinstance([1,2,3,4],Iterable) # 判断列表是否是可迭代对象                 
Out:    True   

In:     isinstance((1,2,3,4),Iterable) # 判断元组是否是可迭代对象
Out:    True

In:     isinstance("abcdef",Iterable) # 判断字符串是否是可迭代对象
Out:    True

In:     isinstance({'name':'zht','age':25,'school':'zhbit'},Iterable)  # 判断字典是否是可迭代对象
Out:    True

In:     isinstance(100,Iterable)  # 判断数字(Numbers)是否是可迭代对象
Out:    False

3、推导式

list(range(1, 5))  # 注意range(1, 5)是左闭右开,1 2 3 4
# [1, 2, 3, 4]
[x*x for x in range(1, 5)]
# [1, 4, 9, 16]
[x for x in range(1, 10) if x % 2 == 0]
# [2, 4, 6, 8]
# 过滤掉长度小于等于3的字符串列表,并将剩下的转换成大写字母
names = ['Bob', 'Tom', 'alice', 'Jerry', 'Wendy', 'Smith']
[name.upper() for name in names if len(name) > 3]

# ['ALICE', 'JERRY', 'WENDY', 'SMITH']
# 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表
[(x, y) for x in range(6) if x % 2 == 0 for y in range(6) if y % 2 != 0]

# [(0, 1), (0, 3), (0, 5), (2, 1), (2, 3), (2, 5), (4, 1), (4, 3), (4, 5)]

4、生成器

通过列表生成式,我们可以直接创建一个列表。但是假如我们有如下推导式,创建100万个元素的列表。这个list就占用很大内存。如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

[x*x for x in range(1, 1000000)]

所以,如果列表元素可以按照某种算法推算出来(推导式或函数),那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。

在Python中,这种一边循环一边计算的机制,称为生成器(generator)


4.1、创建生成器

  • 方法1:即把列表生成式的[ ] 改成 ( )——生成器表达式

推导式:

l=[x for x in range(1, 10) if x % 2 == 0]
l
# [2, 4, 6, 8]
print(type(l))
# <class 'list'>
# 通过for循环打印每个元素
for i in l:
    print(i)

# 2
# 4
# 6
# 8

生成器:把列表推导式的[ ] 改成( )

g=(x for x in range(1, 10) if x % 2 == 0)
g  # 如下结果,generator object,即生成器对象
# <generator object <genexpr> at 0x000001E0798AB040>
print(type(g)) # 打印g的类型
# <class 'generator'>

生成器保存的是算法,可以通过next()函数获得生成器的下一个元素的值。每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

next(g)
# 2
next(g)
# 4
next(g)
# 6
next(g)
# 8
next(g)
# StopIteration: 

我们创建了一个生成器后,基本上永远不会用next()调取,而是通过for循环来迭代它,并且不需要关心StopIteration的错误。既然生成器可以被for循环迭代,那么生成器也是可迭代对象

g1 = (x for x in range(1, 10) if x % 2 == 0) # 注意range(1, 10)是左闭右开,1 2 3 4 5 6 7 8 9
for i in g1:
    print(i)
# 2
# 4
# 6
# 8

  • 方法2:函数加 yield()关键字——生成器函数

比如我们有一个f1(max)函数,输入最大值,然后从0开始打印至最大值。如下:

def f1(max):
    i=0
    while i <=max:
        print(i)
        i+=1
    return None
f1(10)

# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10

那如何将f1(max)函数修改成生成器呢?只需要把 print(i) 改为 yield(i) 就可以了

  • 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个生成器。
  • 区分普通函数和生成器函数,普通函数调用直接返回结果,生成器函数的“调用”实际返回一个生成器对象。
def f1(max):
    i=0
    while i <=max:
        yield(i)  # yield关键字
        i+=1
    return None
f1(10)
# <generator object f1 at 0x000001E078DE24A0>
for i in f1(10):   # 通过for循环调出
    print(i)

# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10

我们在定义一个与f1一样的生成器f2,并用next函数调用,如下:

def f2(max):
    i=0
    while i <=max:
        yield(i)
        i+=1
    return None
g = f2(10)
next(g)
# 0
next(g)
# 1
next(g)
# 2
next(g)
# 3

4.2、生成器的执行流程

  • 函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
  • 变成生成器的函数,每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
In:     def gen():
            print("步骤1")
            yield(1)
            print("步骤2")
            yield(3)
            print("步骤3")
            yield(5)
        
        g=gen()
        print(type(g))
Out:    <class 'generator'>

In:     next(g)
Out:    步骤1
        1

In:     next(g)
Out:    步骤2
        3

In:     next(g)
Out:    步骤3
        5

In:     next(g)
Out:    ---------------------------------------------------------------------------
        StopIteration                             Traceback (most recent call last)
        <ipython-input-24-e734f8aca5ac> in <module>
        ----> 1 next(g)
        
        StopIteration: 

5、迭代器

  • 我们现在已经知道,ListTupleDictSetStr等数据类型、以及生成器都可以用for进行迭代,故他们都是可迭代对象。
  • 生成器不但可以被for进行迭代,还可以被next()函数进行调用,直到抛出StopIteration异常
  • 可以被next()函数调用并不断返回下一个值的对象称为迭代器
  • 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退
  • 迭代器有两个基本的方法:iter()next()

为什么ListTupleDictSetStr等数据类型是可迭代对象,但不是迭代器呢?

  • 这是因为迭代器对象是一个数据流,里面只保存算法
  • 可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以迭代器的计算是惰性的,只有在需要返回下一个数据时它才会计算
  • 迭代器的甚至可以表示一个无限大的数据流,例如全体自然数。而使用list等数据类型是永远不可能存储全体自然数的
  • 可以利用iter()函数将ListTuple等数据类型转化为迭代器
In:     # 字符串创建迭代器
        str1="abcdefgh"
        iter1=iter(str1)
        print(type(iter1))
Out:    <class 'str_iterator'>  

In:     # 列表创建迭代器
        list1=[1,2,3,4,5]
        iter2=iter(list1)
        print(type(iter2))
Out:    <class 'list_iterator'>

In:     # 元组创建迭代器
        tup1=(1,2,3,4,5)
        iter3=iter(tup1)
        print(type(iter3))
Out:    <class 'tuple_iterator'>

6、迭代器和生成器的区别

共同点

  • 生成器与迭代器,都可以被next()调取,故生成器也是一种迭代器。

不同点

  • 语法上:
    生成器是通过函数的形式中调用 yield()的形式创建的(生成器函数与生成器表达式);而迭代器可以通过 iter()内置函数创建。
  • 用法上:
    生成器在调用next()函数或for循环中,所有过程被执行,且返回值;迭代器在调用next()函数或for循环中,所有值被返回,没有其他过程或动作。





posted @ 2022-03-26 22:44  taoshushu  阅读(40)  评论(0编辑  收藏  举报