Home  >  Article  >  Backend Development  >  What is the function of the keyword yield in Python?

What is the function of the keyword yield in Python?

silencement
silencementOriginal
2019-05-23 11:28:309709browse

In Python, the role of the yield keyword: 1. Modify a function into a generator. Using the generator can effectively save system resources and avoid unnecessary memory usage; 2. Used to define the context manager ; 3. Coroutine; 4. Cooperate with from to form yield from, which is used to consume sub-generators and deliver messages.

What is the function of the keyword yield in Python?

The usage of yield has the following four common situations:

  • One is a generator,

    To summarize: the code inside the generator will return when it reaches yield, and the returned content is the expression after yield. The next time the generator's internal code is executed, it will continue from the last state. Through the yield keyword, we can easily modify a function into a generator.

  • The second one is used to define the context manager,

  • The third one is the coroutine,

  • The fourth is to cooperate with from to form yield from, which is used to consume sub-generators and deliver messages.

These four usages are actually derived from the pause feature of yield. That is to say, when the program runs to the location where yield is result = yield expr, it first executes yield expr. Returns the resulting value to the caller that called the generator, then pauses until the caller becomes active again and resumes execution of the program. Depending on the method used by the recovery program, the result value of the yield expr expression will also change accordingly. If called using __next()__, the value result of the yield expression is None; if called using send(), the value result of the yield expression is the value passed through the send function. The following is an example of the yield expression introduced in the official document [1], which can well illustrate the characteristics and usage of the keyword yield:

>>> def echo(value=None):
...     print("Begin...")
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     finally:
...         print("Clean up!!!")
...
>>> generator = echo(1)
>>> print(next(generator))
Begin...
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam')
>>> generator.close()
Clean up!!!

The description of the above code is as shown below:

What is the function of the keyword yield in Python?

  • When executing the first next(generator), that is, preactivating the generator, the generator starts executing, prints the Begin... string, and executes When reaching the position of value = (yield value), first call yield value to generate the number 1, and then the generator pauses at the position of yield.

  • When the second next(generator) is called, the generator resumes execution. Since next() is used to call the generator function, the value of value will become None, so it is generated When the interpreter function continues execution to yield value, the value of value None will be returned to the interpreter, and then paused again.

  • Then use the send(2) method to continue calling the generator. value receives the incoming number 2, continues to execute value = (yield value), and returns the number 2 to the interpreter. then pause.

  • After that, the interpreter calls again through the throw(TypeError, "spam") method. The generator resumes execution and throws an exception. The generator catches the exception and passes the exception TypeError( 'spam') is assigned to the variable value, and then the program is executed again to value = (yield value), and TypeError('spam') is returned to the interpreter.

  • Finally, the program calls the close() method, throws GeneratorExit at the location of the generator function, the exception is thrown, the generator exits normally, and finally executes the corresponding outermost try statement The finally branch prints Clean up.

#There is a very useful syntax in Python called generator, and the keyword used is yield. Effective use of the generator tool can effectively save system resources and avoid unnecessary memory usage.

Generator

As expected, the first time you encounter yield will definitely be in a generator function. A generator is a function that is used to continuously generate numbers or other types of values. It can be called one by one through a for loop or the next() function. What needs to be emphasized here is that the generator contains a yield expression without assignment, so the following two forms are equivalent [2]:

def integers_1():
    for i in range(4):
        yield i + 1def integers_2():
    for i in range(4):
        value = yield i + 1

The reason why the second form is emphasized here is for Yield can be better understood when understanding sending value through the send() method. At the same time, it can also be explained more correctly that the value returned by calling the generator is the value of the expression i 1 on the right side of the yield keyword, not the result value of the yield expression itself.

Let’s try to call:

>>> for n in integers_1():
...     print(n)
...
1
2
3
4
>>> for n in integers_2():
...     print(n)
...
1
2
3
4

Context manager

With the @contextmanager decorator in Python’s contexlib module, yield can also be used For defining context managers, the following is an example from the Python Tricks book [3]:

from contextlib import contextmanager

@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

The context manager defined above through the decorator and yield keyword is equivalent to the method definition of the following class:

class ManagedFile:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        self.file = open(self.name, 'w')
        return self.file
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

can be called separately using the following methods:

>>> with ManagedFile('hello.txt') as f:
...     f.write('hello, world!')
...     f.write('bye now')

>>> with managed_file('hello.txt') as f:
...     f.write('hello, world!')
...     f.write('bye now')

Coroutine

The concept of coroutine is full of beauty and is very consistent with people’s work patterns. It still takes some effort to fully master it. But the effort is worth it, because sometimes multi-threading can cause far more trouble than coroutines. The following is an example of a coroutine written using only yield expressions in the Python Cookbook [4]: ​​

from collections import deque

# Two simple generator functions
def countdown(n):
    while n > 0:
        print('T-minus', n)
        yield
        n -= 1
    print('Blastoff!')

def countup(n):
    x = 0
    while x < n:
        print(&#39;Counting up&#39;, x)
        yield
        x += 1

class TaskScheduler:
    def __init__(self):
        self._task_queue = deque()

    def new_task(self, task):
        &#39;&#39;&#39;
        Admit a newly started task to the scheduler

        &#39;&#39;&#39;
        self._task_queue.append(task)

    def run(self):
        &#39;&#39;&#39;
        Run until there are no more tasks
        &#39;&#39;&#39;
        while self._task_queue:
            task = self._task_queue.popleft()
            try:
                # Run until the next yield statement
                next(task)
                self._task_queue.append(task)
            except StopIteration:
                # Generator is no longer executing
                pass

# Example use
sched = TaskScheduler()
sched.new_task(countdown(2))
sched.new_task(countup(5))
sched.run()

运行上面的脚本,可以得到以下输出:

T-minus 2
Counting up 0
T-minus 1
Counting up 1
Blastoff!
Counting up 2
Counting up 3
Counting up 4

countdown 和 countup 两个任务交替执行,主程序在执行到 countdown 函数的 yield 表达式时,暂停后将被重新附加到队列里面。然后,countup 任务从队列中取了出来,并开始执行到 yield 表达式的地方后暂停,同样将暂停后的协程附加到队列里面,接着从队列里取出最左边的任务 countdown 继续执行。重复上述过程,直到队列为空。

上面的协程可以利用 Python3.7 中的 asyncio 库改写为:

import asyncio

async def countdown(n):
    while n > 0:
        print(&#39;T-minus&#39;, n)
        await asyncio.sleep(0)
        n -= 1
    print(&#39;Blastoff!&#39;)

async def countup(n):
    x = 0
    while x < n:
        print(&#39;Counting up&#39;, x)
        await asyncio.sleep(0)
        x += 1

async def main():
    await asyncio.gather(countdown(2), countup(5))

asyncio.run(main())

可以看到利用 asyncio 库编写的协程示例比用 yield 来编写的协程要优雅地多,也简单地多,更容易被人理解。

yield from

说实话,yield from 实在有点令人费解,让人摸不着头脑。yield from 更多地被用于协程,而 await 关键字的引入会大大减少 yield from 的使用频率。yield from 一方面可以迭代地消耗生成器,另一方面则建立了一条双向通道,可以让调用者和子生成器便捷地通信,并自动地处理异常,接收子生成器返回的值。下面是 Python Cookbook 书里的一个例子,用于展开嵌套的序列[5]:

from collections.abc import Iterable

def flatten(items, ignore_types=(str, bytes)):
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):
            yield from flatten(x)
        else:
            yield x

items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
    print(x)

而 yield from 用于建立双向通道的用法则可以参考 Fluent Python 里例子[6],这里就不详细地解释这段代码:

# BEGIN YIELD_FROM_AVERAGER
from collections import namedtuple

Result = namedtuple(&#39;Result&#39;, &#39;count average&#39;)


# the subgenerator
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)


# the delegating generator
def grouper(results, key):
    while True:
        results[key] = yield from averager()


# the client code, a.k.a. the caller
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)

    report(results)


# output report
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(&#39;;&#39;)
        print(f&#39;{result.count:2} {group:5} averaging {result.average:.2f}{unit}&#39;)


data = {
    &#39;girls;kg&#39;:
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    &#39;girls;m&#39;:
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    &#39;boys;kg&#39;:
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    &#39;boys;m&#39;:
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}


if __name__ == &#39;__main__&#39;:
    main(data)

可能对于熟练掌握 Python 的程序员来说,yield 和 yield from 相关的语法充满了美感。但对于刚入门的我来说,除了生成器语法让我感觉到了美感,其他的语法都让我理解起来很是费解。不过还好,asyncio 库融入了 Python 的标准库里,关键字 async 和 await 的引入,将会让我们更少地在编写协程时去使用 yield 和 yield from。 但不管怎么样,yield 都是 Python 里非常特别的一个关键字,值得花时间好好掌握了解。

The above is the detailed content of What is the function of the keyword yield in Python?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:How to sort python tuplesNext article:How to sort python tuples