5. 从生成器过渡到协程:yield

在线wifi跑包 金刚包跑包 cap跑包 hccapx ewsa在线 就来 握手包跑包

各位好 又见面了 我是曹操 今天给大家带来一篇新的教程

希望各位细心学习 低调用网

import collections
from collections.abc import Iterable, Iterator, Generator
# 字符串
astr = "XiaoMing"
print("字符串:{}".format(astr))
print(isinstance(astr, Iterable))
print(isinstance(astr, Iterator))
print(isinstance(astr, Generator))
# 列表
alist = [21, 23, 32,19]
print("列表:{}".format(alist))
print(isinstance(alist, Iterable))
print(isinstance(alist, Iterator))
print(isinstance(alist, Generator))
# 字典
adict = {"name": "小明", "gender": "男", "age": 18}
print("字典:{}".format(adict))
print(isinstance(adict, Iterable))
print(isinstance(adict, Iterator))
print(isinstance(adict, Generator))
# deque
adeque=collections.deque('abcdefg')
print("deque:{}".format(adeque))
print(isinstance(adeque, Iterable))
print(isinstance(adeque, Iterator))
print(isinstance(adeque, Generator))

学习协程的困难

在学习协程的过程中,我发现网上很难找到一篇系统而全面的文章,导致我们往往只能半知半解,学完后还是一脸懵逼。因此,学习协程的第一门课程是要先了解生成器,因为只有掌握了生成器的基础,才能更好地理解协程。

可迭代、迭代器、生成器

初学Python时,对于可迭代、迭代器和生成器这三个概念常常分不清楚,甚至会认为它们是等价的。实际上,它们是不同的。

可迭代的对象很好理解,我们熟悉的有字符串、列表、字典、元组、双端队列等。为了验证我所说的,我们可以使用collections.abc模块(Python 2中没有),通过isinstance()来判断一个对象是否可迭代(Iterable)、是否是迭代器(Iterator)、是否是生成器(Generator)。

需要注意的是,这几个判断方法在大部分情况下适用,但并不是绝对适用,具体原因请参考后续的补充说明。

字符串:XiaoMing
True
False
False
列表:[21, 23, 32, 19]
True
False
False
字典:{'name': '小明', 'gender': '男', 'age': 18}
True
False
False
deque:deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
True
False
False

字典生成器

可迭代对象的补充说明

在补充说明可迭代对象时,有几点需要注意:

  1. 可以通过dir()方法查看对象是否有__iter__属性,若有,则说明该对象是可迭代的。但是,如果对象没有__iter__属性,并不能说明它不可迭代。这是因为,当对象没有__iter__属性时,Python解释器会尝试按顺序(从索引0开始)通过__getitem__方法获取元素,如果不抛出异常,则可以认为该对象是可迭代的。因此,判断对象是否可迭代最好的方法是通过for循环或者iter()函数进行实际运行。

以上是关于可迭代对象的补充说明。大家可以通过实际运行来验证这些概念,加深对可迭代、迭代器和生成器的理解。

from collections.abc import Iterable, Iterator, Generator
class MyList(object):  # 定义可迭代对象类
    def __init__(self, num):
        self.end = num  # 上边界
    # 返回一个实现了__iter__和__next__的迭代器类的实例
    def __iter__(self):
        return MyListIterator(self.end)
class MyListIterator(object):  # 定义迭代器类
    def __init__(self, end):
        self.data = end  # 上边界
        self.start = 0
    # 返回该对象的迭代器类的实例;因为自己就是迭代器,所以返回self
    def __iter__(self):
        return self
    # 迭代器类必须实现的方法,若是Python2则是next()函数
    def __next__(self):
        while self.start < self.data:
            self.start += 1
            return self.start - 1
        raise StopIteration
if __name__ == '__main__':
    my_list = MyList(5)  # 得到一个可迭代对象
    print(isinstance(my_list, Iterable))  # True
    print(isinstance(my_list, Iterator))  # False
    # 迭代
    for i in my_list:
        print(i)
    my_iterator = iter(my_list)  # 得到一个迭代器
    print(isinstance(my_iterator, Iterable))  # True
    print(isinstance(my_iterator, Iterator))  # True
    # 迭代
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))

迭代器

相比可迭代对象,迭代器只是多了一个函数。这个函数就是__next__(),我们可以使用next()方法来逐个获取元素值,而不再需要使用for循环。

迭代器是在可迭代对象的基础上实现的。要创建一个迭代器,首先需要有一个可迭代对象。现在让我们来看看如何创建一个可迭代对象,并以可迭代对象为基础创建一个迭代器。

0
1
2
3
4
True
False
True
True
0
1
2
3
4
from collections.abc import Iterator
aStr = 'abcd'  # 创建字符串,它是可迭代对象
aIterator = iter(aStr)  # 通过iter(),将可迭代对象转换为一个迭代器
print(isinstance(aIterator, Iterator))  # True
next(aIterator)  # a
next(aIterator)  # b
next(aIterator)  # c
next(aIterator)  # d
# 使用列表生成式,注意不是[],而是()
L = (x * x for x in range(10))
print(isinstance(L, Generator))  # True

生成器

生成器的概念首次出现在Python 2.2中。引入生成器的目的是为了实现在计算下一个值时不浪费空间的结构。

前面我们提到,迭代器是在可迭代对象的基础上添加了next()方法。而生成器则是在迭代器的基础上(可以使用for循环,可以使用next()方法),再实现了yield关键字。

yield相当于函数中的return语句。每次调用next()或进行for循环时,yield会将新的值返回,并在此处阻塞,等待下一次的调用。正是由于这个机制,生成器在Python编程中发挥了重要作用,实现了节省内存和异步编程。

下面是创建生成器的两种主要方法:

  1. 使用生成器函数:定义一个函数,并在函数体中使用yield关键字来产生值。每次调用生成器函数时,都会返回一个生成器对象。

  2. 使用生成器表达式:类似于列表推导式,但使用圆括号而不是方括号。生成器表达式可以直接生成一个生成器对象。

以上是关于生成器的介绍以及创建生成器的两种方法。生成器的特性使得它在Python编程中具有独特的优势,可以实现高效的内存管理和异步编程。

# 实现了yield的函数
def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1
if __name__ == '__main__':
    gen = mygen(10)
    print(isinstance(gen, Generator))  # True
def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1
if __name__ == '__main__':
    gen = mygen(4)
    # 通过交替执行,来说明这两种方法是等价的。
    print(gen.send(None))
    print(next(gen))
    print(gen.send(None))
    print(next(gen))

运行/激活生成器

可迭代对象和迭代器会将所有的值都生成并存放在内存中,而生成器则是在需要元素时临时生成,从而节省时间和空间。

那么,如何运行或激活生成器呢?由于生成器不会一次性生成所有元素,而是逐个执行并返回,我们需要激活生成器来执行它的代码块。下面介绍两种激活生成器的方法。

  1. 使用next()函数:通过调用next()函数来激活生成器并获取下一个值。每次调用next()函数时,生成器会从上次暂停的位置继续执行,并返回下一个值。

  2. 使用for循环:使用for循环来遍历生成器对象,会自动激活生成器并获取每个值。for循环会在每次迭代时自动调用next()函数,直到生成器没有更多的值可返回。

通过以上两种方法,我们可以激活生成器并逐个获取其生成的值。

以上是关于如何运行/激活生成器的介绍。通过实际运行下面的例子,你就能更好地理解了。

0
1
2
3
from inspect import getgeneratorstate
def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1
if __name__ == '__main__':
    gen = mygen(2)
    print(getgeneratorstate(gen))
    print(next(gen))
    print(getgeneratorstate(gen))
    print(next(gen))
    gen.close()  # 手动关闭/结束生成器
    print(getgeneratorstate(gen))

生成器的执行状态

生成器在其生命周期中会有以下四个状态:

  1. GEN_CREATED:等待开始执行状态,表示生成器已经创建但还未开始执行。

  2. GEN_RUNNING:解释器正在执行状态(只有在多线程应用中才能看到这个状态),表示生成器正在被解释器执行。

  3. GEN_SUSPENDED:在yield表达式处暂停状态,表示生成器在执行过程中遇到yield表达式并暂停。

  4. GEN_CLOSED:执行结束状态,表示生成器已经执行完毕。

通过以下代码示例,可以感受一下生成器的不同执行状态。为了简化代码理解难度,我不会举例GEN_RUNNING状态,如果你对此感兴趣,可以尝试在多线程应用中观察。

`python
def my_generator():
yield 1
yield 2
yield 3

gen = my_generator()

print(gen.giframe.flasti) # 输出: -1,表示生成器处于GEN_CREATED状态

print(next(gen))
print(gen.giframe.flasti) # 输出: 6,表示生成器处于GEN_SUSPENDED状态

print(next(gen))
print(gen.giframe.flasti) # 输出: 12,表示生成器处于GEN_SUSPENDED状态

print(next(gen))
print(gen.giframe.flasti) # 输出: 18,表示生成器处于GEN_SUSPENDED状态

执行完所有yield语句后,生成器处于GEN_CLOSED状态

`

以上是关于生成器的执行状态的介绍。如果有任何疑问,可以在后台回复给我。

GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED

字典生成器

生成器的异常处理

在生成器的工作过程中,如果生成器不满足生成元素的条件,就会/应该抛出异常(StopIteration)。通过列表生成式构建的生成器,其内部已经自动帮我们实现了抛出异常这一步。

让我们来看一个例子,以验证这一点:

`python
gen = (x for x in range(5))

print(next(gen)) # 输出: 0
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
print(next(gen)) # 输出: 4
print(next(gen)) # 抛出StopIteration异常
`

通过以上代码,我们可以看到,在使用列表生成式构建的生成器中,当生成器没有更多的值可返回时,会自动抛出StopIteration异常。

以上是关于生成器的异常处理的说明。生成器在工作过程中,通过抛出异常来表示生成结束,我们可以在代码中进行相应的异常处理。

def mygen(n):
    now = 0
    while now < n:
        yield now
        now += 1
    raise StopIteration
if __name__ == '__main__':
    gen = mygen(2)
    next(gen)
    next(gen)
    next(gen)
def jumping_range(N):
    index = 0
    while index < N:
        # 通过send()发送的信息将赋值给jump
        jump = yield index
        if jump is None:
            jump = 1
        index += jump
if __name__ == '__main__':
    itr = jumping_range(5)
    print(next(itr))
    print(itr.send(2))
    print(next(itr))
    print(itr.send(-1))

自定义生成器的异常处理

在自定义生成器时,我们也应该在不满足生成元素条件的情况下抛出异常。让我们修改上面的代码来演示这一点:

`python
def my_generator():
for x in range(5):
yield x

    if x == 3:
        raise StopIteration("StopIteration exception raised")

gen = my_generator()

try:
while True:
print(next(gen))
except StopIteration as e:
print(e)
`

通过以上代码,我们在生成器中添加了一个条件,当x等于3时,抛出StopIteration异常。在主程序中,我们使用while循环和next()函数来逐个获取生成器的值,当生成器抛出异常时,我们捕获并打印异常信息。

以上是关于自定义生成器的异常处理的示例。

从生成器过渡到协程:yield

通过前面的介绍,我们知道生成器引入了暂停函数执行的功能(yield)。当生成器暂停时,我们可以向其发送一些信息。这种向暂停的生成器发送信息的功能在Python 2.5中通过PEP 342引入,并催生了Python中协程的诞生。

根据维基百科的定义,协程是为非抢占式多任务产生子程序的计算机程序组件,允许不同入口点在不同位置暂停或开始执行程序。

需要注意的是,协程并不属于语言本身的概念,而是一种编程模型的概念。

协程和线程有一些相似之处,多个协程和线程一样,只会交替串行执行。但也有一些不同之处,线程之间需要频繁进行切换、加锁和解锁,从复杂度和效率的角度来看,与协程相比,这是一个痛点。协程通过使用yield来暂停生成器,可以将程序的执行流程交给其他子程序,从而实现不同子程序之间的交替执行。

下面通过一个简单的演示来展示如何向生成器中发送消息。

`python
def my_generator():
while True:
x = yield
print(“Received:”, x)

gen = my_generator()
next(gen) # 激活生成器

gen.send(“Hello”) # 向生成器发送消息
`

通过以上代码,我们创建了一个生成器,使用yield暂停生成器的执行,并在接收到消息时打印出来。我们通过send()方法向生成器发送消息,生成器会在yield处恢复执行,并打印出接收到的消息。

以上是关于向生成器发送消息的示例。

0
2
3
2

解释输出结果

让我们解释一下为什么会有这样的输出结果,重点是jump = yield index这个语句。

在上面的代码中,我们创建了一个生成器函数my_generator(),其中有一个关键的语句jump = yield index。这个语句可以分成两部分来理解:

  1. yield index:这部分表示生成器会在这里暂停执行,并将index的值返回给调用者。也就是说,每次调用生成器的send()方法时,会将发送的值赋给index,并在此处暂停。

  2. jump =:这部分表示将yield表达式的值赋给jump变量。也就是说,当生成器恢复执行时,会将send()方法发送的值赋给jump

在主程序中,我们激活生成器并调用了send()方法来向生成器发送消息。首先,我们调用next(gen)来激活生成器并执行到第一个yield语句处,此时index的值为0。然后,我们调用gen.send("Jump")来向生成器发送消息,这个消息会被赋值给jump变量。生成器恢复执行,继续执行后面的代码,并打印出indexjump的值。

因此,输出结果为:


index: 0, jump: None
index: 1, jump: Jump

以上是对输出结果的解释。

实践和理解

以上这些内容都是关于协程并发的基础必备知识。请务必亲自去实践并理解它们,否则后面的内容将会变得枯燥无味、晦涩难懂。

通过实践和理解这些基础知识,你将能够更好地理解和应用后续的内容。

祝你在学习协程并发的过程中取得进步!

赞(0)