Python非同期オーバーホールへの3つのステップ

Pythonは、非同期プログラムを作成する方法をサポートする多くの言語の1つです。プログラムは、複数のタスク間で自由に切り替わり、すべてが一度に実行されるため、1つのタスクが他のタスクの進行を妨げることはありません。

ただし、主に同期Pythonプログラムを作成している可能性があります。これは、一度に1つのことだけを実行し、各タスクが完了するのを待ってから別のタスクを開始するプログラムです。非同期への移行は、新しい構文だけでなく、コードについての新しい考え方も学ぶ必要があるため、不快感を与える可能性があります。 

この記事では、既存の同期プログラムを非同期プログラムに変換する方法について説明します。これには、関数を非同期構文で装飾するだけではありません。また、プログラムの実行方法について異なる考え方をし、非同期がその機能の優れたメタファーであるかどうかを判断する必要もあります。 

[また、SerdarYegulalpのSmartPythonビデオからPythonのヒントとコツを学びましょう]

Pythonで非同期を使用する場合

Pythonプログラムは、次の特性がある場合に非同期に最適です。

  • これは、長時間実行されるネットワーク読み取りのように、ほとんどがI / Oに拘束されるか、外部プロセスが完了するのを待つことによって何かを行おうとしています。
  • これらの種類のタスクの1つ以上を一度に実行しようとしている一方で、ユーザーの操作も処理している可能性があります。
  • 問題のタスクは計算量が多くありません。

スレッド化を使用するPythonプログラムは、通常、非同期を使用するのに適しています。Pythonのスレッドは協調的です。彼らは必要に応じてお互いに譲ります。Pythonの非同期タスクは同じように機能します。さらに、非同期にはスレッドに比べて特定の利点があります。

  • async/のawait構文は、それが簡単にあなたのプログラムの非同期部分を識別することができます。対照的に、アプリのどの部分がスレッドで実行されているかを一目で判断するのは難しいことがよくあります。 
  • 非同期タスクは同じスレッドを共有するため、アクセスするデータはすべてGIL(オブジェクトへのアクセスを同期するためのPythonのネイティブメカニズム)によって自動的に管理されます。多くの場合、スレッドは同期のために複雑なメカニズムを必要とします。 
  • 非同期タスクは、スレッドよりも管理とキャンセルが簡単です。

Pythonプログラムに次の特性がある場合は、非同期の使用はお勧めしません

  • タスクの計算コストは​​高くなります。たとえば、大量の数値計算を実行します。重い計算作業はmultiprocessing、で処理するのが最適です。これにより、ハードウェアスレッド全体を各タスクに割り当てることができます。
  • タスクは、インターリーブされてもメリットがありません。各タスクが最後のタスクに依存している場合、それらを非同期で実行する意味はありません。とはいえ、プログラムに一連のシリアルタスクが含まれている場合は、 各セットを非同期で実行できます。

ステップ1:プログラムの同期部分と非同期部分を特定する

Python非同期コードは、Pythonアプリケーションの同期部分によって起動され、管理される必要があります。そのために、プログラムを非同期に変換するときの最初のタスクは、コードの同期部分と非同期部分の間に線を引くことです。

非同期に関する前回の記事では、簡単な例としてWebスクレイパーアプリを使用しました。コードの非同期部分は、ネットワーク接続を開き、サイトから読み取るルーチンです。これは、インターリーブするすべてのものです。しかし、それをすべて開始するプログラムの部分は非同期ではありません。非同期タスクを起動し、終了すると正常に終了します。

また、ブロックする可能性のある操作を非同期から分離し、アプリの同期部分に保持することも重要 です。たとえば、コンソールからユーザー入力を読み取ると、非同期イベントループを含むすべてがブロックされます。したがって、非同期タスクを起動する前または終了した後に、ユーザー入力を処理する必要があります。(マルチプロセッシングまたはスレッド化を介してユーザー入力を非同期で処理すること可能ですが、これここで取り上げない高度な演習です。)

ブロッキング操作のいくつかの例:

  • コンソール入力(今説明したように)。
  • CPU使用率が高いタスク。
  • time.sleep一時停止を強制するために使用します。のasyncio.sleep代わりにを使用すると、非同期関数内でスリープできることに注意してくださいtime.sleep

ステップ2:適切な同期関数を非同期関数に変換する

プログラムのどの部分が非同期で実行されるかがわかったら、それらを関数に分割し(まだ実行していない場合)、asyncキーワードを使用してそれらを非同期関数に変換できます。次に、アプリケーションの同期部分にコードを追加して、非同期コードを実行し、必要に応じてそのコードから結果を収集する必要があります。

注:非同期にした各関数の呼び出しチェーンをチェックし、それらが潜在的に長時間実行またはブロック操作を呼び出していないことを確認する必要があります。非同期関数は同期関数を直接呼び出すことができ、その同期関数がブロックされると、それを呼び出す非同期関数もブロックされます。

同期から非同期への変換がどのように機能するかについての簡単な例を見てみましょう。これが私たちの「前」プログラムです:

def a_function():#しばらく時間がかかる非同期互換アクションdef another_function():#同期関数がありますが、1つをブロックしていませんdef do_stuff():a_function()another_function()def main():for _ in range (3):do_stuff()main() 

の3つのインスタンスをdo_stuff非同期タスクとして実行する場合は、do_stuff (および場合によってはそれに触れるすべてのものを)非同期コードに変換する必要があります。変換の最初のパスは次のとおりです。

import asyncio async def a_function():#しばらく時間がかかる非同期互換アクションdef another_function():#同期関数がありますが、1つの非同期関数をブロックしていませんdef do_stuff():a_function()another_function()async def main( ):tasks = [] for _ in range(3):tasks.append(asyncio.create_task(do_stuff()))await asyncio.gather(tasks)asyncio.run(main()) 

に加えた変更に注意してください main。をmain 使用asynciodo_stuffて、の各インスタンスを並行タスクとして起動し、結果を待ちます(asyncio.gather)。またa_function、すべてのインスタンスをa_function並べて実行し、非同期動作を必要とする他の関数と一緒に実行する必要があるため、非同期関数に変換しました。

さらに一歩進めたい場合はanother_function、非同期に変換することもできます。

async def another_function():#いくつかの同期関数ですが、1つをブロックしていませんasync def do_stuff():await a_function()await another_function() 

ただし、 another_function 非同期にすることは、(すでに述べたように)プログラムの進行を妨げるようなことは何もしないため、やり過ぎになります。また、プログラムの同期部分がと呼ばれる another_function場合は、それらも非同期に変換する必要があります。これにより、プログラムが必要以上に複雑になる可能性があります。

ステップ3:Python非同期プログラムを徹底的にテストする

非同期変換されたプログラムは、本番環境に移行する前にテストして、期待どおりに機能することを確認する必要があります。

If your program is modest in size — say, a couple of dozen lines or so — and doesn’t need a full test suite, then it shouldn’t be difficult to verify that it works as intended. That said, if you’re converting the program to async as part of a larger project, where a test suite is a standard fixture, it makes sense to write unit tests for async and sync components alike.

Both of the major test frameworks in Python now feature some kind of async support. Python’s own unittest framework includes test case objects for async functions, and pytest offers pytest-asyncio for the same ends.

Finally, when writing tests for async components, you’ll need to handle their very asynchronousness as a condition of the tests. For instance, there is no guarantee that async jobs will complete in the order they were submitted. The first one might come in last, and some might never complete at all. Any tests you design for an async function must take these possibilities into account.

How to do more with Python

  • Get started with async in Python
  • How to use asyncio in Python
  • How to use PyInstaller to create Python executables
  • Cython tutorial: How to speed up Python
  • How to install Python the smart way
  • How to manage Python projects with Poetry
  • How to manage Python projects with Pipenv
  • Virtualenv and venv: Python virtual environments explained
  • Python virtualenv and venv do’s and don’ts
  • Python threading and subprocesses explained
  • How to use the Python debugger
  • timeitを使用してPythonコードをプロファイリングする方法
  • cProfileを使用してPythonコードをプロファイリングする方法
  • PythonをJavaScriptに変換する方法(そしてまた元に戻す方法)