【python】生成器是什么?

生成器是什么?

在Python中,生成器(Generator)是一种特殊类型的函数,它可以用于迭代(iteration)操作。

生成器函数使用关键字yield而不是return来返回值,这使得它能够在每次迭代时产生一个值,并在下一次迭代时继续执行。生成器在每次迭代中只计算并返回一个值,这样可以有效地节省内存空间。

生成器函数的结构类似于普通函数,但其特点是在执行过程中会暂停并保存当前的状态,等待下一次迭代时继续执行。生成器可以通过yield语句返回一个值,并且可以使用for循环或者next()函数来迭代生成器中的值。

创建生成器的方式?

创建生成器的方式有两种:

  1. 通过 生成器表达式
  2. 通过 生成器函数

生成器表达式
简单快捷,足以胜任简单的生成器创建工作,其形式和 列表推导式 类似,只不过将中括号 [] 替换成了小括号 ().

In [1]: generator = (x * x for x in range(8))

In [2]: type(generator)
Out[2]: generator

In [3]: lst = [x * x for x in range(8)]

In [4]: type(lst)
Out[4]: list

生成器函数
要让一个 函数 成为 生成器函数 很简单,只需要将函数中的 return 替换为 yield

def fib(num):
    n, a, b = 0, 0, 1
    while n < num:
        yield b
        a, b = b, a + 1
        n = n + 1
        return 'done'


f = fib(6)

print(type(f))  # 

生成器函数中,每次执行到 yield 语句就会返回,再次执行时从上次返回的 yield 语句处继续执行。

生成器和迭代器有什么关系?

from collections.abc import Generator
from collections.abc import Iterator

print(issubclass(Generator, Iterator))  # True

print(set(dir(Generator)) - set(dir(Iterator)))  # 生成器有三个专属方法 {'throw', 'close', 'send'}

生成器迭代器在 Python 中有密切的关系,可以说生成器是一种特殊类型的迭代器

迭代器(Iterator)是一个对象,它实现了迭代协议,即具有__iter__()__next__()方法。迭代器可以使用iter()函数进行迭代,并使用next()函数获取下一个元素。迭代器每次返回一个值,并在迭代过程中记录自身的状态。

生成器是一种特殊的迭代器,可以通过生成器函数创建。生成器函数使用yield语句来产生值,每次调用生成器的next()方法时,生成器会执行并返回yield语句产生的值,然后暂停保存状态。这意味着生成器具有迭代器的所有特性,可以被for循环和next()函数使用。

迭代器是一种更通用的概念,它可以通过实现迭代协议来创建。而生成器是一种特殊类型的迭代器,它通过生成器函数的方式来创建,并具有延迟计算和状态保存的特性。因此,可以说生成器是迭代器的一种实现方式,是迭代器的一种特例。

生成器有什么用?

生成器具有以下特点:

  1. 延迟计算:生成器是惰性计算的,它们在需要时才生成值,可以逐个地生成并处理数据。这样可以节省内存,并且在处理大量数据或者无限序列时非常高效。
  2. 状态保存:生成器能够保存自身的状态,每次调用next()方法时从上次暂停的地方继续执行。这使得生成器能够处理需要记住状态或上下文的任务。
  3. 简洁性:使用生成器函数可以以一种简洁的方式实现迭代器,并且代码可读性较高。

生成器函数和生成器对象

前面我们已经了解了什么是生成器,以及如何创建生成器。这里我们看下生成器比较容易混淆的两个概念和生成器的运行原理:

def gen(num):
    while num > 0:
        yield num
        num -= 1
   return 100
   
# 使用生成器函数创建生成器对象
g = gen(5)
first = next(g)

# 迭代生成器中的值
for i in g:
    print(i)
  • gen(num)是生成器函数。
  • g = gen(num)调用生成器函数得到生成器对象,即g是生成器对象。

在给定的代码中,定义了一个生成器函数gen(num),它使用yield语句产生从num到1的倒序序列。当num大于0时,通过yield语句返回num的值,并在下一次迭代时继续执行。当num小于等于0时,生成器函数会执行完毕并返回值100

下面是代码的执行过程:

  1. 创建生成器对象g,并将num参数设为5。
  2. 调用next(g),生成器开始执行,进入循环。
  3. yield num语句产生值5,并将生成器暂停。此时first变量被赋值为5
  4. 开始for循环迭代生成器g,从下一次迭代开始,即num4
  5. 在每次迭代中,yield num语句产生当前的num值,并将生成器暂停。被产生的值依次为4321
  6. num变为0时,生成器函数执行完毕,返回值100
  7. for循环结束,没有更多的值可迭代。

因此,代码的输出结果将是:

4
3
2
1

请注意,return语句在生成器函数中不会被执行,因为生成器的返回值是由yield语句产生的值决定的,而不是通过return语句返回的。在这个例子中,return 100语句不会影响生成器的输出结果。

在进行若干次之后, 这个时候它会运行这个return, 由于它(return)是在生成器函数里面了, 所以return等价于raise stop iteration。

这个return不管你有没有return值,它都不会在你调用next的时候被返回回去。如果你想拿到这个return value的话,你需要去catch那个stop iteration的exception。

生成器在处理大量数据或者无限序列时非常有用,因为它们可以逐个生成并处理数据,而不需要一次性将所有数据加载到内存中。这种惰性计算的特性使得生成器在处理大型数据集或者需要逐步计算的任务时具有很高的效率。

改写迭代器版链表遍历

迭代器版链表迭代

#! -*-conding=: UTF-8 -*-
# 2023/5/22 18:43

class NodeIter:
    def __init__(self, node):
        self.curr_node = node

    def __next__(self):
        if self.curr_node is None:
            raise StopIteration
        node, self.curr_node = self.curr_node, self.curr_node.next

        return node


class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):
        return NodeIter(self)


node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")

node1.next = node2
node2.next = node3


if __name__ == '__main__':
    for node in node1:
        print(node.name)

生成器版链表迭代

#! -*-conding=: UTF-8 -*-
# 2023/5/22 18:43

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

    def __iter__(self):
        node = self
        while node is not None:
            yield node
            node = node.next


node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")

node1.next = node2
node2.next = node3


if __name__ == '__main__':
    for node in node1:
        print(node.name)

链表本身的iterator,把这个node变成了一个iterable, 这一次我们也是想把这个node变成iterable。但是这个iter函数我们直接把它做成一个生成器,也就是当你做iter node的时候,它返回的是一个generator object。

某些情况下,生成器实现的链表相比迭代器版是不是清爽简洁多了。

生成器对象

生成器的完整名称应该是生成器对象, 既然是一个对象,那么自然有 方法 和 属性

g = (x for x in range(10))
help(g)
Help on generator object:
<genexpr> = class generator(object)
 |  Methods defined here:
 |  
 |  __del__(...)
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  close(...)
 |      close() -> raise GeneratorExit inside generator.
 |  
 |  send(...)
 |      send(arg) -> send 'arg' into generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  throw(...)
 |      throw(typ[,val[,tb]]) -> raise exception in generator,
 |      return next yielded value or raise StopIteration.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  gi_code
 |  
 |  gi_frame
 |  
 |  gi_running
 |  
 |  gi_yieldfrom
 |      object being iterated by yield from, or None

send方法

send()函数是用于与生成器进行交互的一种方法。它允许在生成器的每次暂停(yield)点发送值,并且该值将成为yield表达式的结果。

下面是关于send()函数的一些重要事项:

  1. yield语句:yield语句是生成器函数中的特殊语句,它用于生成值并将生成器暂停。当生成器执行到yield语句时,它会产生一个值,并将该值返回给调用方。同时,生成器会保留它的状态,以便在下一次迭代时从暂停的地方继续执行。
  2. send()函数:send()函数用于向生成器发送值,并恢复生成器的执行。它的语法为generator.send(value),其中generator是生成器对象,value是要发送给生成器的值。send()函数会将值发送给生成器,并将该值作为yield表达式的结果。同时,生成器会从上一次暂停的地方继续执行,并执行到下一个yield语句或函数结束。

需要注意的是,当首次调用生成器时,必须使用next()函数来启动生成器,或者使用send(None),将None作为参数发送给生成器。这是因为生成器在第一次调用时需要被激活,以准备接收来自send()函数的值。

send不接收

def gen(num):
    while num > 0:
        yield num
        num -= 1

    return 100


# 使用生成器函数创建生成器对象
g = gen(2)
first = next(g)  # first =g.send(none)

print(f"first: {first}")

print(f"first: {g.send(5)}")

# 迭代生成器中的值
for i in g:
    print(i)

输出结果为:

first: 2
first: 1

yield num没有被赋值给任何东西,g.send(5)相当于被扔掉了。

send接收

def gen(num):
    while num > 0:
        tmp = yield num
        if tmp is not None:
            num = tmp
        num -= 1
    return 100


# 使用生成器函数创建生成器对象
g = gen(2)
first = next(g)  # first =g.send(none)

print(f"first: {first}")
print(f"send: {g.send(5)}")

# 迭代生成器中的值
for i in g:
    print(i)

输出结果为:

first: 2
send: 4
3
2
1

close方法

生成器对象的close()方法用于关闭生成器。调用close()方法后,生成器会抛出一个GeneratorExit异常,表示生成器已关闭。

关闭生成器有以下几个效果:

  • 当生成器在执行过程中遇到yield语句时,会抛出GeneratorExit异常,可以在生成器内部通过捕获该异常来执行一些清理操作。
  • 后续对生成器的迭代操作会立即停止,不会再产生新的值。
  • 如果生成器函数定义了finally语句块,那么在生成器关闭时,finally语句块中的代码会被执行。
def gen():
    try:
        for i in range(10):
            yield i
    except GeneratorExit:
        print('close generator')


g = gen()

print(next(g))

g.close()

print(next(g))

输出结果为:

0
close generator
Traceback (most recent call last):
  File "F:/study/pythonProjects/ss.py", line 15, in <module>
    print(next(g))
StopIteration

throw方法

生成器提供了一个throw()方法,允许在生成器内部引发一个指定的异常。

throw()方法的语法如下:

generator.throw(exception[, traceback])
  • exception参数是要引发的异常对象。
  • traceback参数是可选的,用于指定异常的回溯信息(traceback)。

当调用generator.throw()方法时,生成器会在当前的yield表达式上引发指定的异常。然后,异常会在生成器的当前位置被捕获,可以通过使用try/except语句来处理异常。

下面是一个示例代码,演示了生成器的throw()方法的使用:

def my_generator():
    try:
        yield 1
        yield 2
        yield 3
    except ValueError as e:
        print(f"Caught exception: {e}")

gen = my_generator()

print(next(gen))  # 输出: 1
print(next(gen))  # 输出: 2

gen.throw(ValueError("Custom exception"))  # 引发异常,并在生成器内部捕获

print(next(gen))  # 输出: 3

在上述示例中,my_generator()函数是一个生成器函数,它会生成数值 1、2 和 3。在生成器中,我们使用了try/except语句来捕获ValueError异常。当调用gen.throw()方法并传递一个ValueError异常时,生成器会在当前的yield表达式上引发该异常,并在生成器内部捕获。

输出结果为:

1
2
Caught exception: Custom exception
3

注意,在生成器捕获异常后,生成器会继续执行后续的yield表达式。在上述示例中,即使引发了异常,yield 3语句仍然会被执行,并返回最后一个值 3。

它的实现手段是通过向生成器对象在上次被挂起处,抛出一个异常。之后会继续执行生成器对象中后面的语句,直至遇到下一个yield语句返回。如果在生成器对象方法执行完毕后,依然没有遇到yield语句,抛出StopIteration异常。

总结

生成器是一种特殊的迭代器(注意这个逻辑关系反之不成立)。使用生成器,你可以写出来更加清晰的代码;合理使用生成器,可以降低内存占用、优化程序结构、提高程序速度。


喜欢这篇文章的话,可以关注一下我的公众号『海哥python

你可能感兴趣的:(python基础,python)