Pythonのコルーチン

コルーチンとして、Pythonのこのような興味深いがほとんど使用されていない機能について説明することを提案します。
Pythonのコルーチンはジェネレーターに基づいています(実際、ジェネレーターはそうです)。
したがって、一般的な意味でジェネレーターから始めることを提案します。 そして、コルーチンの書き方を見つけます。

発電機


多かれ少なかれまともなPythonプログラマーは、ジェネレーター関数のような素晴らしいものがあることを知っています。 主な機能は、呼び出し間で状態を保持することです。

つまり、あなたはそれらがどのように機能するかを知っているので、私はあなたにそれがどのように見えるか思い出させます。
この関数を見てみましょう:

def read_file_line_by_line(file_name): with open(file_name, 'r') as f: while True: line = f.readline() if not line: break yield line 


この関数は、ファイル名を入力として受け取り、それを完全にメモリにロードせずに1行ずつ返します。これは、大きなファイルを読み込むときに必要になる場合があります。
この手法は遅延読み取りと呼ばれ、不必要に「動作」しないことを意味します。

一般に、ジェネレーターの操作は次のとおりです。

 In [78]: lines_generator = read_file_line_by_line("data.csv") In [79]: type(lines_generator) Out[79]: generator In [83]: lines_generator.next() Out[83]: 'time,host,event\n' In [84]: lines_generator.next() Out[84]: '1374039728,localhost,reboot\n' In [85]: lines_generator.next() Out[85]: '1374039730,localhost,start\n' In [86]: lines_generator.next() --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-86-65df1a2cb71b> in <module>() ----> 1 lines_generator.next() StopIteration: #       3  #     ,   StopIteration,      . 


当然ながら、行ごとではなく、ループ内のジェネレーターから値を読み取ることがよくあります。
この単純な方法で、任意の大きなファイルからすべての一意の行を取得できます。

 uniq = [] for line in lines_generator: if line not in uniq: uniq.append(line) 


ジェネレーターの短い記録も可能です。

 In [92]: gen = (x for x in xrange(0, 100*10000)) In [93]: gen.next() Out[93]: 0 In [94]: gen.next() Out[94]: 1 In [95]: gen.next() Out[95]: 2 In [96]: gen.next() Out[96]: 3 In [97]: gen.next() Out[97]: 4 


リスト式のようですね。 メモリrange(0, 100*10000)リスト全体を作成する必要はありません。戻り値はアクセスされるたびに「計算」されます。

ジェネレーターの特殊なケースとしてのコルーチン


そして今、それが実際に始まった理由について。 ジェネレーターは値を返すだけでなく、入力で値を受け入れることもできます。

PEP 342で標準について読むことができます。

例からすぐに始めることをお勧めします。 2つの引数を追加し、結果の履歴を保存し、履歴を表示できるジェネレーターの簡単な実装を作成します。

 def calc(): history = [] while True: x, y = (yield) if x == 'h': print history continue result = x + y print result history.append(result) c = calc() print type(c) # <type 'generator'> c.next() #  .   c.send(None) c.send((1,2)) #  3 c.send((100, 30)) #  130 c.send((666, 0)) #  666 c.send(('h',0)) #  [3, 130, 666] c.close() #   


つまり ジェネレータを作成して初期化し、入力データを供給しました。
次に、彼はこのデータを処理し、 それを閉じるまで呼び出し間でその状態維持します。 呼び出しごとに、ジェネレーターは制御を呼び出した場所に戻します 。 これは、使用するジェネレーターの最も重要なプロパティです。

だから、それがどのように機能するかは整理されているようです。
ここで、毎回手でジェネレーターを初期化する必要性から自分を救いましょう。
デコレータを使用して、Pythonの典型的な方法でこれを解決します。

 def coroutine(f): def wrap(*args,**kwargs): gen = f(*args,**kwargs) gen.send(None) return gen return wrap @coroutine def calc(): history = [] while True: x, y = (yield) if x == 'h': print history continue result = x + y print result history.append(result) 


この例では、より複雑な(そして有用な)コルーチンの書き方を理解できます。

おわりに


このツールで解決できる問題は多くの分野(非同期プログラミングなど)をカバーしていますが、多くの開発者はより使い慣れたOOPツールを好みます。 しかし同時に、コルーチンは非常に視覚的であり、クラスのオブジェクトを作成するよりも安価な操作なので、コルーチンは武器庫で非常に便利なツールになります。

そして、私にはそう思われるように、彼らは特定の学問的関心を表しています。

これが最初の記事です。

UPD:
xrange rangeジェネレーターの短いレコードの例で修正されました。
2番目のバージョンでは、python range()はリスト全体を一度に作成し、3番目のバージョンではxrange()を使用する必要があるジェネレーターを作成しますrange == xrange (つまり、ジェネレーターを返します)。

Source: https://habr.com/ru/post/J196918/


All Articles