迭代器和生成器以及yield关键字的用法

迭代器和生成器以及yield关键字的用法

一、概述

我们在刚开始学习python数据结构的时候,主要学习的是列表、集合、字典等几种基本的数据结构,但是随着学习的深入,我们会接触到容器、可迭代对象、迭代器、生成器等一系列让人容易产生混淆的概念。下面我们就来捋一捋它们之间的关系以及它们各自的含义。

二、可迭代的对象与迭代器的对比

使用iter内置函数可以获取迭代器的对象,如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。而可迭代的对象和迭代器之间的关系是:python从可迭代的对象中获取迭代器。
标准的迭代器接口有两个方法
__next__方法:返回下一个可用的元素,如果没有元素了,就会抛出StopIteration异常。
__iter__方法:返回self,以便在应该使用可迭代对象的地方使用迭代器,例如在for循环中。
例如我们用for循环对一个简单的字符串’hsyy‘进行迭代,这里字符串’hsyy’是可迭代的对象。

s = 'hsyy'
for char in s:
   print(char)

输出:

h
s
y
y

如果没有for循环,那么我们就不得不使用while循环模拟for循环,那么代码就应该这样写:

s = 'hsyy'
it = iter(s)#使用可迭代对象构建迭代器it
while True:
    try:
        print(next(it))#不断在迭代器上调用next函数,获取下一个字符。
    except StopIteration:#如果没有字符了,迭代器会抛出StopIteration异常。
        del it#释放对it的引用,即废弃迭代器对象。
        break#退出循环

输出:

h
s
y
y

代码中的StopIteration异常表明迭代器到头了。通过以上for循环和while循环模拟我们可以看出,python语言内部会处理for循环中的StopIteration异常,从中也可以看出for循环内部会调用可迭代对象的iter方法返回一个迭代器对象,然后不断调用迭代器对象的next方法。

迭代的好处:

迭代是数据处理的基石。如果我们要扫描内存中放不下的数据集时,我们可以通过迭代来惰性获取数据项,即按需一次获取一个数据项。

三、 生成器

生成器有两种创建方式:

第一种:使用生成器表达式

生成器表达式背后遵循了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,显然能够节约内存。

s = (x*2 for x in range(5))
print(s)

输出:

<generator object <genexpr> at 0x000001F3363415E8>

很显然s是一个生成器对象。

第二种:使用yield关键字创建生成器函数

只要python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。

补充:yield关键字

• yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器
• 当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
• 当你使用for进行迭代的时候,函数中的代码才会执行

def Create_Generator():
    list  = range(3)
    for i in list:
        yield i*i

my_generator = Create_Generator()
print(my_generator)

for j in Create_Generator():
    print(j)

输出:

<generator object Create_Generator at 0x0000027F454617C8>
0
1
4

从以上代码我们可以看出:从第一次迭代时函数Create_Generator()就会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的产出值,然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个产出值,直到没有可以返回的。

下面我们通过一个简单的函数来说明生成器的行为:

def foo():
    yield 1
    yield 2
    yield 3

print(foo)
print(foo())

输出:

<function foo at 0x0000024FDC94AA60>
<generator object foo at 0x0000024FDAD017C8>

从输出可以看出foo是函数对象,但是调用生成器函数foo()时,会返回一个生成器对象。

def foo():
    yield 1
    yield 2
    yield 3

for i in foo():
    print(i)

输出:

1
2
3

从输出可以看出,当使用for循环对生成器函数进行迭代时,函数中的代码才会执行。

def foo():
    yield 1
    yield 2
    yield 3
g = foo()
print(next(g))
print(next(g))
print(next(g))

输出:

1
2
3

从以上代码可以看出,包装生成器函数的定义体,把生成器传给next()函数,当第二次把生成器传给next()函数时,生成器函数会从上次函数执行结束时的下一条yield语句开始执行,并返回产出的值。

四、 总结

• 凡是生成器都是迭代器,但是迭代器不一定是生成器;
• 凡是可作用于next()函数的对象都是Iterator(迭代器)类型,它们表示一个惰性计算的序列;
• 凡是可作用于for循环的对象(一类是集合数据类型,如list、tuple、dict、set、str等;另一类是generator,包括 生成器和带yield的generator function。)都是Iterable(可迭代)类型,不过Iterable(可迭代)类型可以通过iter()函数获得一个Iterator对象;
• 调用生成器函数返回生成器,生成器产出或生成值。生成器不会以常规的方式‘返回’值。生成器函数中的return语句会触发生成器对象抛出StopIteration异常。
• 用yield关键字好处:不会将所有数据取出来存入内存中,而是返回了一个对象,可以通过对象获取数据,用多少取多少,可以节省内存空间。

猜你喜欢

转载自blog.csdn.net/hongsong673150343/article/details/86556924
今日推荐