Pythonにおけるコルーチン

概要

一言で表すと「関数を途中でストップ・リスタートする機能」です。

デバッガ(pdb.set_trace())の途中でストップするのと少し似ているかもしれません。

例題

呼び出すと、引数をそのままprintしてくれる関数を作ってみます。

サンプルコード

# 参考: https://qiita.com/koshigoe/items/054383a89bd51d099f10
import functools


def main():
    receiver1 = test_coroutine1()
    next(receiver1)    # 初回のyield文までたどり着くために必要。
    receiver1.send(1)  # => v: 1
    receiver1.send(2)  # => v: 2
    receiver1.send(3)  # => v: 3

    receiver2 = test_coroutine2()
    receiver2.send(10)  # => v: 10
    receiver2.send(20)  # => v: 20
    receiver2.send(30)  # => v: 30


def coroutine(func):
    @functools.wraps(func)
    def wrapper(*args, **kargs):
        g = func(*args, **kargs)
        next(g)
        return g
    return wrapper


def test_coroutine1():
    while True:
        print("---")
        v = yield
        print("v: {}".format(v))


@coroutine
def test_coroutine2():
    while True:
        print("---")
        v = yield
        print("v: {}".format(v))


if __name__ == "__main__":
    main()

特徴

基本は以下です。

def test_coroutine1():
    while True:
        print("---")
        v = yield
        print("v: {}".format(v))

receiver1 = test_coroutine1()
next(receiver1)    # 初回のyield文までたどり着くために必要。
receiver1.send(1)  # => v: 1

処理の概要は以下のようになっています。

・test_coroutine1が呼ばれた時点では、関数の中は1文字も処理されていません。

next(receiver1)の実行により、yieldの行まで処理が進みます。
print("---")がなされる)

・その後はreceiver1.send(引数)とすると、処理が進みます。
yield構文があると、またそこで処理が止まります。

test_coroutine2について

test_coroutine2関数はnextがなくても、最初からsendを送ることができる状態となっています。

これはデコレータによるものです。

詳細な機能はDecorator - Pyデザパタに譲りますが、
デコレータは、関数の前に特定の処理(coroutine関数の中の処理)を行うことができます。

そのため、関数を動かす前に、必ずnext()を挟むようになっています。

実際はデコレータつきのコルーチンが作られることの方が多いようです。

使われるところ

一般的には非同期処理などで使用される例などがあります。

コルーチンを使用することでデザインパターンを効率的に書くことができることもあるようです。