揭开yield关键字的神秘面纱

图片 1

写在前言

1.迭代

在理解生成器之前,先理解迭代。

经常会看见,python函数中带有yield关键字,那么yield是什么,有什么作用?

1.1 迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)

alist = [1, 2, 3, 4, 5]

for i in alist:
    print(i)

1
2
3
4
5

正如将列表中的元素通过for循环,遍历了整个alist列表,这种不重复地便利其内部的每一个子项的行为就是迭代。

 

1.2 可迭代对象

可以直接作用于for循环的对象统称为可迭代对象:Iterable,可迭代对象一般都实现了__iter()__方法,可迭代对象通过其内建的方__iter()__返回一个迭代器对象。

a_iterable = [1, 2, 3]

a_iterator = iter(a_iterable)  # 将可迭代对象转化为迭代器

next(a_iterator)

1

next(a_iterator)

2

next(a_iterator)

3

答案:可以理解yield是一个生成器;

1.3 迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator,迭代器其内实现了__iter__方法和__next__方法,for循环本质是通过调用可迭代对象的__iter__方法,该方法返回一个迭代器对象,再用__next__方法遍历元素

定义一个迭代器:

class MyRange:
    def __init__(self, end):
        self.index = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < self.end:
            val = self.index
            self.index += 1
            return val
        else:
            raise StopIteration()

my_range = MyRange(3)

print([i for i in my_range])

[0, 1, 2]

print([i for i in my_range])

[]

迭代器只能迭代一次,每次调用调用 next()
方法就会向前一步,不能后退,所以当迭代器迭代到最后时,就不可以重复利用,所有需要将迭代器和可迭代对象分开定义

修改上面的可迭代对象:

class MyRange:
    def __init__(self, end):
        self.end = end

    def __iter__(self):
        return MyIterator(self.end)

class MyIterator:
    def __init__(self, end):
        self.index = 0
        self.end = end

    def __iter__(self):
        return self    

    def __next__(self):
        if self.index < self.end:
            val = self.index
            self.index += 1
            return val
        else:
            raise StopIteration()

my_range = MyRange(3)

print([i for i in my_range])

[0, 1, 2]

print([i for i in my_range])

[0, 1, 2]

作用:遇到yield关键字,函数会直接返回yield值,相当于return;不同的是下次调用的时候会从yield之后的代码开始执行。

2. 生成器

生成器与可迭代对象、迭代器的关系

图片 1

图片来自Iterables vs. Iterators vs.
Generators

生成器对象,在每次调用它的next()方法时返回一个值,直到它抛出StopInteration。

生成器是可以迭代的,但是你 只可以读取它一次
,因为它并不把所有的值放在内存中,它是实时地生成数据,
可以用生成器表达式创建:

my_generator = (x ** 2 for x in range(3))

my_generator

<generator object <genexpr> at 0x7f975b7a4af0>

for i in my_generator:
    print(i)

0
1
4

yield

可以写一个普通的包含yield语句的Python函数,Python会检测对yield的使用并将函数标记为一个生成器,当函数执行到yield语句时,像return语句那样返回一个值,但是解释器会保存对栈的引用,它会被用来在下一次调用next时恢复函数。

def my_generator():
    yield 1
    yield 2
    yield 'a'
    yield 'generator'

g = my_generator()

g

<generator object my_generator at 0x7f975b7a4d58>

next(g)

1

next(g)

2

next(g)

'a'

next(g)

'generator'

next(g)

---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-12-5f315c5de15b> in <module>()
----> 1 next(g)


StopIteration: 

上面的例子中,每次调用next()开始实时地生成数据,并返回,因此生成器只可读取一次,上次执行读取的值在下次执行中就无法读取。当整个生成器的值都被读取后,在调用机会出现StopIteration的错误。

def my_gen():
    for i in range(5):
        yield i ** 3

my_gen()

<generator object my_gen at 0x7f975ae15a40>

mygen = my_gen()

for i in mygen:
    print(i)

0
1
8
27
64

每次执行到yield语句,则返回一个值,再执行的时候从上次停下来的地方开始执行。yield语句保存了上次执行后的状态,下次执行不是从头开始,而是从上次的状态开始。

当调用my_gen()这个函数的时候,函数内部的代码不会立即执行,而是返回一个生成器对象,当利用for循环进行遍历的时候,函数内部的代码开始执行,执行到yield表达式返回一个值,记录当前状态并停下,下一次的访问时再从这个状态开始执行。

举一个不太恰当的例子,普通的函数就是没有存档的游戏,只要游戏开始,就玩到结尾,下一次再玩还是从头开始,而生成器就是加了存档,下次玩从上次存档的地方开始

 

关于生成器的思考

(瞎掰的。。。。)生成器到底起到什么吗作用呢,就算生成一个生成器对象,而生成器对象一定是一个迭代器,所以可以这么说,生成器返回了一个可以用for循环遍历所以子项,可以用next()方法访问下一个子项,可以在访问时动态的生成数据而节省内存的对象。

生成器是什么?

阅读

完全理解 Python
迭代对象、迭代器、生成器
对 Python
迭代的深入研究
Python迭代器和生成器
3.
(译)Python关键字yield的解释(stackoverflow)
Python之列表生成式、生成器、可迭代对象与迭代器

是可以迭代的,但是你
只可以读取它一次
,因为它并不把所有的值放在内存中,它是实时地生成数据:

 

有人可能会说,我直接迭代,遍历多好,为什么要用生成器,然后去遍历生成器,这多麻烦。

那么你要了解,list列表,所有数据是存储在内存中的。如果数据量非常大,会非常耗内存。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注