Pythonのyieldはコルーチンとメインルーチンの接続ポイントとして理解する

2 min read読了の目安(約1600字

まずイテレータ。

class hoge(object):
	def __init__(self, *nums):
		self._nums = nums
		self._i = 0
	def __iter__(self):
		return self
	def __next__(self):
		if self._i == len(self._nums):
			raise StopIteration()
		v = self._nums[self._i]
		self._i += 1
		return value
itr = hoge(10, 20, 30)
for v in itr:
    print(v)
# 10
# 20
# 30

これはイテレータを定義したクラスです。__next__ではfor文で次の値をとるときに呼ばれるPython規定メソッドです(magic method)。例えばリストは一番シンプルなイテレータの一種です。init, iter, nextの3つのmagic methodが呼べるオブジェクトをイテレータといいます。

次にジェネレータ。

def hoge():
	print("hello")
	yield 4
	print("world")
	yield 2
h = hoge() # h is a generator
print(h.__next__())
# hello
# 4
print(h.__next__())
# world
# 2

みたいに呼び出すたびに答えが変わる特殊な変数をジェネレタといいます。__next__というのは「次の値を出せ」という命令です。この命令はイテレータで使われています。

yieldが呼ばれた時点で処理が中断し、次に呼ばれたときに次の行から処理が再開することがわかります。この処理の中断と再開が許可されているサブルーチンをコルーチンと呼びます。(ちなみにプロセスの基礎となるメインルーチンから呼ばれるのがサブルーチンです。普通のメソッドや関数です)

しかしyieldを処理を中断しメインルーチンに処理を返すキーワードとして捉えているとコルーチンの理解ができません。

def hoge():
	print("hello")
	v = yield
	print("my")
	print(v)
	v = yield
	print("world")

h = hoge() # h is a generator
h.__next__()
# hello
h.send(4)
# my
# 4
h.send(5)
# world
# 5

この例ではh.__next__()でyieldまでいってv = yieldの部分でメインルーチンから呼ばれるのを待ちます(中断)。メインルーチンではpythonコルーチンに規定されているsendメソッドを使ってコルーチンに向けて情報を送ります。コルーチンは情報を受け取ると中断したところから再開します。

returnはサブルーチンからメインルーチンへ情報を返すだけのキーワードですがyieldはサブルーチンからメインルーチンへ情報を返すだけでなくメインルーチンからサブルーチンに情報を送ることもできます。

こうした中断と再開がありメインルーチンと通信しあって動くサブルーチンをコルーチンといい、その接続ポイントがyieldです。(ちなみにcoroutineのcoはTiffany & CoのCoです。メインルーチンと協調しあうサブルーチンという意味です。)したがってyieldがメインルーチンに情報を返すだけでなくメインルーチンから情報を受け取れるというのはコルーチンのためのキーワードとして至極真っ当なのです。