纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

yield的用法 在Python中怎样使用yield

vincent_duan   2021-06-07 我要评论
想了解在Python中怎样使用yield的相关内容吗vincent_duan在本文为您仔细讲解yield的用法的相关知识和一些Code实例欢迎阅读和指正我们先划重点:yield的用法,python,yield下面大家一起来学习吧

一、生成器

如果在一个方法内包含了 yield 关键字那么这个函数就是一个「生成器」

生成器其实就是一个特殊的迭代器它可以像迭代器那样迭代输出方法内的每个元素

我们来看一个包含 yield 关键字的方法:

# coding: utf8

# 生成器
def gen(n):
    for i in range(n):
        yield i

g = gen(5)      # 创建一个生成器
print(g)        # <generator object gen at 0x10bb46f50>
print(type(g))  # <type 'generator'>

# 迭代生成器中的数据
for i in g:
    print(i)
    
# Output:
# 0 1 2 3 4

注意在这个例子中当我们执行 g = gen(5) 时gen 中的代码其实并没有执行此时我们只是创建了一个「生成器对象」它的类型是 generator

然后当我们执行 for i in g每执行一次循环就会执行到 yield 处返回一次 yield 后面的值

这个迭代过程是和迭代器最大的区别

换句话说如果我们想输出 5 个元素在创建生成器时这个 5 个元素其实还并没有产生什么时候产生呢?只有在执行for循环遇到 yield 时才会依次生成每个元素

此外生成器除了和迭代器一样实现迭代数据之外还包含了其他方法:

  • generator.__next__():执行 for 时调用此方法每次执行到 yield 就会停止然后返回 yield 后面的值如果没有数据可迭代抛出 StopIterator 异常for 循环结束
  • generator.send(value):外部传入一个值到生成器内部改变 yield 前面的值
  • generator.throw(type[, value[, traceback]]):外部向生成器抛出一个异常
  • generator.close():关闭生成器

通过使用生成器的这些方法我们可以完成很多有意思的功能

二、next

先来看生成器的 __next__ 方法我们看下面这个例子

# coding: utf8

def gen(n):
    for i in range(n):
        print('yield before')
        yield i
        print('yield after')

g = gen(3)      # 创建一个生成器
print(g.__next__())  # 0
print('----')
print(g.__next__())  # 1
print('----')
print(g.__next__())  # 2
print('----')
print(g.__next__())  # StopIteration

# Output:
# yield before
# 0
# ----
# yield after
# yield before
# 1
# ----
# yield after
# yield before
# 2
# ----
# yield after
# Traceback (most recent call last):
#   File "gen.py", line 16, in <module>
#     print(g.__next__())  # StopIteration
# StopIteration

在这个例子中我们定义了 gen 方法这个方法包含了 yield 关键字然后我们执行 g = gen(3) 创建一个生成器但是这次没有执行 for 去迭代它而是多次调用 g.__next__() 去输出生成器中的元素

我们看到当执行 g.__next__()时代码就会执行到 yield 处然后返回 yield 后面的值如果继续调用 g.__next__()注意你会发现这次执行的开始位置是上次 yield 结束的地方并且它还保留了上一次执行的上下文继续向后迭代

这就是使用 yield 的作用在迭代生成器时每一次执行都可以保留上一次的状态而不是像普通方法那样遇到 return 就返回结果下一次执行只能再次重复上一次的流程

生成器除了能保存状态之外我们还可以通过其他方式改变其内部的状态这就是下面要讲的 sendthrow 方法

三、send

上面的例子中我们只展示了在 yield 后有值的情况其实还可以使用 j = yield i 这种语法我们看下面的代码:

# coding: utf8

def gen():
    i = 1
    while True:
        j = yield i
        i *= 2
        if j == -1:
            break

此时如果我们执行下面的代码:

for i in gen():
    print(i)
    time.sleep(1)

输出结果会是 1 2 4 8 16 32 64 ... 一直循环下去 直到我们杀死这个进程才能停止

这段代码一直循环的原因在于它无法执行到 j == -1 这个分支里 break 出来如果我们想让代码执行到这个地方如何做呢?

这里就要用到生成器的 send 方法了send 方法可以把外部的值传入生成器内部从而改变生成器的状态

g = gen()   # 创建一个生成器
print(g.__next__())  # 1
print(g.__next__())  # 2
print(g.__next__())  # 4
# send 把 -1 传入生成器内部 走到了 j = -1 这个分支
print(g.send(-1))   # StopIteration 迭代停止

当我们执行 g.send(-1) 时相当于把 -1 传入到了生成器内部然后赋值给了 yield 前面的 j此时 j = -1然后这个方法就会 break 出来不会继续迭代下去

四、throw

外部除了可以向生成器内部传入一个值外还可以传入一个异常也就是调用 throw 方法:

# coding: utf8

def gen():
    try:
        yield 1
    except ValueError:
        yield 'ValueError'
    finally:
        print('finally')

g = gen()   # 创建一个生成器
print(g.__next__()) # 1
# 向生成器内部传入异常 返回ValueError
print(g.throw(ValueError))

# Output:
# 1
# ValueError
# finally

这个例子创建好生成器后使用 g.throw(ValueError) 的方式向生成器内部传入了一个异常走到了生成器异常处理的分支逻辑

五、close

生成器的 close 方法也比较简单就是手动关闭这个生成器关闭后的生成器无法再进行操作

>>> g = gen()
>>> g.close() # 关闭生成器
>>> g.__next__() # 无法迭代数据
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

close 方法我们在开发中使用得比较少了解一下就好

六、使用场景

了解了 yield 和生成器的使用方式那么 yield生成器一般用在哪些业务场景中呢?

下面我介绍几个例子分别是大集合的生成、简化代码结构、协程与并发你可以参考这些使用场景来使用 yield

大集合的生成

如果你想生成一个非常大的集合如果使用 list 创建一个集合这会导致在内存中申请一个很大的存储空间例如想下面这样:

# coding: utf8

def big_list():
    result = []
    for i in range(10000000000):
        result.append(i)
    return result

# 一次性在内存中生成大集合 内存占用非常大
for i in big_list():
    print(i)

这种场景我们使用生成器就能很好地解决这个问题

因为生成器只有在执行到 yield 时才会迭代数据这时只会申请需要返回元素的内存空间代码可以这样写:

# coding: utf8

def big_list():
    for i in range(10000000000):
        yield i

# 只有在迭代时 才依次生成元素 减少内存占用
for i in big_list():
    print(i)

简化代码结构

我们在开发时还经常遇到这样一种场景如果一个方法要返回一个 list但这个 list 是多个逻辑块组合后才能产生的这就会导致我们的代码结构变得很复杂:

# coding: utf8

def gen_list():
    # 多个逻辑块 组成生成一个列表
    result = []
    for i in range(10):
        result.append(i)
    for j in range(5):
        result.append(j * j)
    for k in [100, 200, 300]:
        result.append(k)
    return result
    
for item in gen_list():
    print(item)

这种情况下我们只能在每个逻辑块内使用 appendlist 中追加元素代码写起来比较啰嗦

此时如果使用 yield 来生成这个 list代码就简洁很多:

# coding: utf8

def gen_list():
    # 多个逻辑块 使用yield 生成一个列表
    for i in range(10):
        yield i
    for j in range(5):
        yield j * j
    for k in [100, 200, 300]:
        yield k
        
for item in gen_list():
    print(i)

使用 yield 后就不再需要定义 list 类型的变量只需在每个逻辑块直接 yield 返回元素即可可以达到和前面例子一样的功能

我们看到使用 yield 的代码更加简洁结构也更清晰另外的好处是只有在迭代元素时才申请内存空间降低了内存资源的消耗

七、协程与并发

还有一种场景是 yield 使用非常多的那就是「协程与并发」

如果我们想提高程序的执行效率通常会使用多进程、多线程的方式编写程序代码最常用的编程模型就是「生产者-消费者」模型即一个进程 / 线程生产数据其他进程 / 线程消费数据

在开发多进程、多线程程序时为了防止共享资源被篡改我们通常还需要加锁进行保护这样就增加了编程的复杂度

在 Python 中除了使用进程和线程之外我们还可以使用「协程」来提高代码的运行效率

什么是协程?

简单来说由多个程序块组合协作执行的程序称之为「协程」

而在 Python 中使用「协程」就需要用到 yield 关键字来配合

可能这么说还是太好理解我们用 yield 实现一个协程生产者、消费者的例子:

# coding: utf8

def consumer():
    i = None
    while True:
        # 拿到 producer 发来的数据
        j = yield i 
        print('consume %s' % j)

def producer(c):
    c.__next__()
    for i in range(5):
        print('produce %s' % i)
        # 发数据给 consumer
        c.send(i)
    c.close()

c = consumer()
producer(c)

# Output:
# produce 0
# consume 0
# produce 1
# consume 1
# produce 2
# consume 2
# produce 3
# consume 3
...

这个程序的执行流程如下:

1.c = consumer() 创建一个生成器对象

2.producer(c) 开始执行c.__next()__会启动生成器 consumer 直到代码运行到 j = yield i 处此时 consumer 第一次执行完毕返回

3.producer 函数继续向下执行直到 c.send(i)处这里利用生成器的 send 方法向 consumer 发送数据

4.consumer 函数被唤醒从 j = yield i 处继续开始执行并且接收到 producer 传来的数据赋值给 j然后打印输出直到再次执行到 yield 处返回

5.producer 继续循环执行上面的过程依次发送数据给 cosnumer直到循环结束

6.最终 c.close() 关闭 consumer 生成器程序退出

在这个例子中我们发现程序在 producerconsumer 这 2 个函数之间来回切换执行相互协作完成了生产任务、消费任务的业务场景最重要的是整个程序是在单进程单线程下完成的


相关文章

猜您喜欢

  • Python 简繁体转换 Python实现简繁体转换

    想了解Python实现简繁体转换的相关内容吗一天一篇Python库在本文为您仔细讲解Python 简繁体转换的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Python,简繁体转换,python,中文简繁体转化下面大家一起来学习吧..
  • VUE SpringBoot分页功能 VUE+SpringBoot实现分页功能

    想了解VUE+SpringBoot实现分页功能的相关内容吗在奋斗的大道在本文为您仔细讲解VUE SpringBoot分页功能的相关知识和一些Code实例欢迎阅读和指正我们先划重点:VUE,SpringBoot,分页下面大家一起来学习吧..

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式