Python におけるキーワード yield の機能は何ですか?

silencement
リリース: 2020-09-18 16:27:58
オリジナル
9704 人が閲覧しました

Python における yield キーワードの役割: 1. 関数をジェネレーターに変更します。ジェネレーターを使用すると、システム リソースを効果的に節約し、不必要なメモリ使用を回避できます。2. コンテキスト マネージャーの定義に使用されます。3.コルーチン; 4. from と連携して、サブジェネレーターを消費してメッセージを配信するために使用される yield from を形成します。

Python におけるキーワード yield の機能は何ですか?

yield の使用には、次の 4 つの一般的な状況があります。

  • 1 つはジェネレーター、

    要約すると、ジェネレーター内のコードは yield に達すると戻り、返される内容は yield 後の式です。次回ジェネレーターの内部コードが実行されるとき、最後の状態から継続されます。 yield キーワードを使用すると、関数をジェネレーターに簡単に変更できます。

  • # 2 つ目はコンテキスト マネージャーの定義に使用されます。

  • 3 つ目はコルーチン

  • 4 番目は、from と連携して、yield from を形成することです。これは、サブジェネレーターを消費してメッセージを配信するために使用されます。

これら 4 つの使用法は、実際には yield の一時停止機能から派生したものです。つまり、プログラムが yield が result = yield expr である場所まで実行されると、最初に yield expr が実行されます。結果の値をジェネレーターを呼び出した呼び出し元に返し、呼び出し元が再びアクティブになるまで一時停止し、プログラムの実行を再開します。回復プログラムで使用される方法に応じて、yield expr 式の結果値もそれに応じて変化します。 __next()__ を使用して呼び出された場合、yield 式の value 結果は None になります。send() を使用して呼び出された場合、yield 式の value 結果は、send 関数を介して渡された値になります。以下は、公式ドキュメント [1] で紹介されている yield 式の例であり、キーワード 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!!!
ログイン後にコピー

上記のコードの説明は以下のとおりです:

Python におけるキーワード yield の機能は何ですか?

  • 最初の next(generator) を実行するとき、つまりジェネレーターを事前にアクティブ化すると、ジェネレーターは実行を開始し、Begin... 文字列を出力します。 value = (yield value) の位置に到達すると、最初に yield value を呼び出して数値 1 を生成し、その後ジェネレータは yield の位置で一時停止します。

  • 2 番目の next(generator) が呼び出されると、ジェネレーターは実行を再開します。 next() を使用してジェネレーター関数を呼び出すため、value の値は None になります。インタープリター関数が値を生成するまで実行を続けると、値 None がインタープリターに返され、再び一時停止されます。

  • 次に、send(2) メソッドを使用してジェネレーターの呼び出しを続けます。value は受信数値 2 を受け取り、value = (生成値) を実行し続け、数値 2 を通訳、その後一時停止します。

  • その後、インタプリタは throw(TypeError, "spam") メソッドを介して再度呼び出します。ジェネレータは実行を再開し、例外をスローします。ジェネレータは例外をキャッチし、例外 TypeError を渡します。 ( 'spam') が変数 value に代入され、次にプログラムが value = (生成値) まで再度実行され、TypeError('spam') がインタプリタに返されます。

  • 最後に、プログラムは close() メソッドを呼び出し、ジェネレーター関数の位置で GeneratorExit をスローします。例外がスローされ、ジェネレーターは正常に終了し、最後に対応する最も外側の try を実行します。ステートメントの最終ブランチは Clean up を出力します。


#Python にはジェネレーターと呼ばれる非常に便利な構文があり、使用されるキーワードは yield です。ジェネレーター ツールを効果的に使用すると、システム リソースが効果的に節約され、不必要なメモリの使用が回避されます。

ジェネレーター

予想どおり、yield に初めて遭遇するのは間違いなくジェネレーター関数内です。ジェネレーターは、数値やその他の種類の値を継続的に生成するために使用される関数で、for ループまたは next() 関数を通じて 1 つずつ呼び出すことができます。ここで強調する必要があるのは、ジェネレーターには代入のない yield 式が含まれているため、次の 2 つの形式は同等であるということです [2]:

def integers_1():
    for i in range(4):
        yield i + 1def integers_2():
    for i in range(4):
        value = yield i + 1
ログイン後にコピー

ここで 2 番目の形式が強調されている理由は、Yield の方が優れているためです。 send() メソッドを介して値を送信することを理解する場合に理解されます。同時に、ジェネレーターの呼び出しによって返される値は、yield 式自体の結果値ではなく、yield キーワードの右側の式 i 1 の値であることも、より正確に説明できます。

呼び出してみましょう:

>>> for n in integers_1():
...     print(n)
...
1
2
3
4
>>> for n in integers_2():
...     print(n)
...
1
2
3
4
ログイン後にコピー

コンテキスト マネージャー

Python の contexlib モジュールの @contextmanager デコレーターを使用すると、コンテキストの定義に yield を使用することもできます。マネージャーの皆さん、以下は Python Tricks 本 [3] の例です。

from contextlib import contextmanager

@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()
ログイン後にコピー

デコレーターと yield キーワードを通じて上記で定義されたコンテキスト マネージャーは、次のクラスのメソッド定義と同等です:

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()
ログイン後にコピー

は、次のメソッドを使用して個別に呼び出すことができます:

>>> 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

コルーチンの概念は美しさに満ちており、人々の作業パターンと非常に一致しています。それを完全にマスターするにはある程度の努力が必要です。しかし、マルチスレッドはコルーチンよりもはるかに多くの問題を引き起こす可能性があるため、努力する価値はあります。以下は、Python クックブック [4] の yield 式のみを使用して記述されたコルーチンの例です。
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 里非常特别的一个关键字,值得花时间好好掌握了解。

以上がPython におけるキーワード yield の機能は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート