現在、ほとんどすべての開発者は、プログラミングにおける「非同期」の概念に精通しています。情報製品の需要が非常に高く、膨大な数の要求を同時に処理し、非同期プログラミングなしで他の多数のサービスと並行して対話することを余儀なくされている時代には、どこにもありません。その必要性は非常に大きいため、別の言語が作成されることさえありました。その主な機能は(最小限であることに加えて)、並列/同時コード、つまりGolangを使用した非常に最適化された便利な作業です。記事は彼に関するものではないという事実にもかかわらず、私はしばしば比較を行い、参照します。しかし、ここPythonでは、この記事で説明します-いくつかの問題について説明し、そのうちの1つに解決策を提供します。このトピックに興味がある場合は、猫の下でお願いします。
たまたま、私が仕事をしたり、ペットプロジェクトを実装したり、休憩したりリラックスしたりするのに私のお気に入りの言語はPythonです。私はその美しさとシンプルさ、その自明性に際限なく魅了されています。その背後には、さまざまな種類の構文上の砂糖の助けを借りて、人間の想像力が可能なほとんどすべての論理を簡潔に説明する大きな機会があります。Pythonは、他の言語で説明するのが非常に難しい抽象化を説明するために使用できるため、超高レベル言語と呼ばれていることをどこかで読んだことがあります。
しかし、1つの深刻なニュアンスがあります-Python並列/同時ロジックを実装する可能性があるため、言語の最新の概念に適合させるのは非常に困難です。言語、つまり80年代に始まり、Javaと同じ時代であるという考えは、特定の時期まで、競争力のあるコードの実行を意味していませんでした。場合はJavaScriptが最初にブラウザで作業を非ブロックするための並行性を必要とし、Golangは現代のニーズの本当の理解と完全に新鮮な言語であり、その後、Pythonは前にこのようなタスクに直面していません。
もちろん、これは私の個人的な意見ですが、組み込みのasyncioライブラリが登場したため、Pythonは非同期の実装に非常に遅れているようです。むしろ、Pythonの同時コード実行の他の実装の出現に対する反応でした。基本的に、asyncioは既存の実装をサポートするように構築されており、独自のイベントループ実装だけでなく、他の非同期ライブラリのラッパーも含まれているため、非同期コードを記述するための共通インターフェイスを提供します。そして、Pythonは、非同期コードを書くときに、上記のすべての要因のために最も簡潔で読みやすい言語として最初に作成されたものであり、デコレータ、ジェネレータ、および関数の寄せ集めになります。特別なディレクティブasyncとawaitを 追加することで状況がわずかに修正されました
それらすべてをリストするのではなく、解決しようとしたものに焦点を当てます。これは、非同期および同期実行の一般的なロジックの説明です。たとえば、Golangで関数を並行して実行したい場合は、goディレクティブを使用して関数を呼び出す必要があります。
Golangでの関数の並列実行
package main
import "fmt"
func function(index int) {
fmt.Println("function", index)
}
func main() {
for i := 0; i < 10; i++ {
go function(i)
}
fmt.Println("end")
}
そうは言っても、Golangでは、これと同じ関数を同期的に実行できます。
Golangでの機能のシリアル実行
package main
import "fmt"
func function(index int) {
fmt.Println("function", index)
}
func main() {
for i := 0; i < 10; i++ {
function(i)
}
fmt.Println("end")
}
でPythonの、すべてのコルーチン(非同期関数)は、それらの間に発生し、スイッチングに基づいて使用して、イベントループに制御を返す機能を遮断する通話中に発生歩留まりディレクティブ。正直なところ、Golangで同時実行性/同時実行性がどのように機能するかはわかりませんが、Pythonの場合と同じように機能しないと言っても間違いありません。GolangコンパイラとCPythonインタープリターの実装の内部には既存の違いがあり、それらの並列処理/同時実行性を比較することは許可されていませんが、実行自体ではなく構文に注意を払います。Python関数を取得して、1人のオペレーターと同時に並行して実行することはできません。私の関数が非同期で機能するためには、宣言の前に明示的に非同期で記述する必要があります。その後、関数は単なる関数ではなく、すでに定型文になっています。また、宣言が類似しているにもかかわらず、Pythonの関数とコルーチンは完全に異なるため、追加のアクションなしで同じコード内でそれらの呼び出しを混在させることはできません。
def func1(a, b):
func2(a + b)
await func3(a - b) # , await
私の主な問題は、同期と非同期の両方で実行できるロジックを開発する必要があることでした。簡単な例は、Instagramと対話するためのライブラリです。これは、ずっと前に放棄しましたが、今は再び使用しました(これにより、解決策を探すようになりました)。同期だけでなく非同期でもAPIを操作する機能を実装したかったのですが、これは単なる要望ではありませんでした。インターネットでデータを収集する場合、多数のリクエストを非同期で送信して、すべてのリクエストにすばやく回答することができますが、同時に、大量のデータ収集はそうではありません。常に必要です。現時点では、ライブラリは以下を実装しています:Instagramを操作するため2つのクラスがあります。1つは同期作業用、もう1つは非同期作業用です。各クラスには同じメソッドのセットがありますが、最初のメソッドは同期であり、2番目のメソッドは非同期です。リクエストがインターネットに送信される方法を除いて、各メソッドは同じことを行います。そして、1つのブロッキングアクションの違いのためだけに、各メソッドのロジックをほぼ完全に複製する必要がありました。次のようになります。
class WebAgent:
def update(self, obj=None, settings=None):
...
response = self.get_request(path=path, **settings)
...
class AsyncWebAgent:
async def update(self, obj=None, settings=None):
...
response = await self.get_request(path=path, **settings)
...
中に他のすべての更新方法とで更新コルーチンは全く同じです。そして多くの人が知っているように、コードの複製は、特にバグの修正とテストに関して、多くの問題を追加します。
この問題を解決するために、独自のpySyncAsyncライブラリを作成しました。考え方は次のとおりです。通常の関数やルーチンの代わりにジェネレーターが実装されます。将来的にはテンプレートと呼びます。テンプレートを実行するには、通常の関数またはコルーチンとしてテンプレートを生成する必要があります。テンプレートは、それ自体の内部で非同期または同期コードを実行する必要があるときに実行されると、yieldを使用して特別なCallオブジェクトを返します。、何を呼び出すか、どの引数を使用するかを指定します。テンプレートが関数またはコルーチンとして生成される方法に応じて、これはCallオブジェクトで記述されたメソッドが実行される方法です。googleに
リクエストを送信する機能を想定したテンプレートの小さな例を示します。
pySyncAsyncを使用したGoogleリクエストの例
import aiohttp
import requests
import pysyncasync as psa
# google
# Call
@psa.register("google_request")
def sync_google_request(query, start):
response = requests.get(
url="https://google.com/search",
params={"q": query, "start": start},
)
return response.status_code, dict(response.headers), response.text
# google
# Call
@psa.register("google_request")
async def async_google_request(query, start):
params = {"q": query, "start": start}
async with aiohttps.ClientSession() as session:
async with session.get(url="https://google.com/search", params=params) as response:
return response.status, dict(response.headers), await response.text()
# 100
def google_search(query):
start = 0
while start < 100:
# Call , google_request
call = Call("google_request", query, start=start)
yield call
status, headers, text = call.result
print(status)
start += 10
if __name__ == "__main__":
#
sync_google_search = psa.generate(google_search, psa.SYNC)
sync_google_search("Python sync")
#
async_google_search = psa.generate(google_search, psa.ASYNC)
loop = asyncio.get_event_loop()
loop.run_until_complete(async_google_search("Python async"))
ライブラリの内部構造について少し説明します。Callを使用して呼び出されるように関数と列が登録されているManagerクラスがあります。テンプレートを登録することも可能ですが、これはオプションです。Managerクラスには、メソッドregister、generate、およびtemplateがあります。上記の例の同じメソッドは、pysyncasyncから直接呼び出されましたが、ライブラリモジュールの1つですでに作成されているManagerクラスのグローバルインスタンスを使用しただけです。実際には、あなたがあなた自身のインスタンスを作成して呼び出すことができるレジスタを、生成し、テンプレート、それから、方法。したがって、たとえば名前の競合が発生する可能性がある場合は、マネージャーを相互に分離します。register
メソッドはデコレータとして機能し、テンプレートからさらに呼び出すために関数またはコルーチンを登録できます。レジスタデコレータは、関数またはコルーチンがマネージャに登録されている名前を引数として受け入れます。名前が指定されていない場合、関数または列は独自の名前で登録されます。template メソッドを使用すると、ジェネレーターをテンプレートとしてマネージャーに登録できます。これは、名前でテンプレートを取得できるようにするために必要です。 メソッドの生成
テンプレートに基づいて関数またはコルーチンを生成できます。 2つの引数を取ります。1つ目はテンプレートまたはテンプレート自体の名前であり、2つ目は関数またはコルーチンに対する「sync」または「async」(テンプレートを生成するもの)です。出力で、generateメソッドは既製の関数またはルーチンを提供します。
たとえば、coroutineでテンプレートを生成する例を示します。
def _async_generate(self, template):
async def wrapper(*args, **kwargs):
...
for call in template(*args, **kwargs):
callback = self._callbacks.get(f"{call.name}:{ASYNC}")
call.result = await callback(*call.args, **call.kwargs)
...
return wrapper
内部では、コルーチンが生成されます。これは、ジェネレーターを繰り返し処理してCallクラスのオブジェクトを受け取り、以前に登録されたコルーチンを名前で取得し(名前はcallから取得)、引数を使用して呼び出し(これもcallから取得)、このコルーチンの実行結果もcallに格納されます。Call
クラスのオブジェクトは、何をどのように呼び出すかに関する情報を格納するための単なるコンテナであり、結果をそれ自体に格納することもできます。ラッパーは、テンプレートの実行結果を返すこともできます。このため、テンプレートは、ここには示されていない特別なGeneratorクラスでラップされます。
ニュアンスの一部は省略しましたが、全体的に本質を伝えていただければ幸いです。
正直なところ、この記事は、Pythonの非同期コードの問題を解決することについての私の考えを共有するために私が書いたものです。そして、最も重要なことは、カブラフの住民の意見に耳を傾けることです。おそらく私は誰かを別のソリューションにぶつけるでしょう、おそらく誰かがこの特定の実装に同意せず、それをより良くする方法を教えてくれるでしょう、おそらく誰かがそのようなソリューションがまったく必要ない理由を教えてくれるでしょう、そしてあなたは同期と非同期コード、あなた方一人一人の意見は私にとって非常に重要です。また、私は記事の冒頭で私のすべての推論に真実であるふりをしません。私は他のYPのトピックについて非常に広範囲に考え、誤解される可能性があります。さらに、突然矛盾に気付いた場合は、概念を混乱させる可能性があります。コメントに記載してください。構文や句読点に変更があれば嬉しいです。
そして、この問題、特にこの記事にご注目いただきありがとうございます。