okpy

Pythonエンジニア兼テックリーダーが、多くのプロジェクトとチーム運営から得た実践的な知識を共有するブログです。

PythonのWebアプリ、もっと速くならない?Sanicなら非同期処理で解決できるかも!

PythonのWebアプリ、もっと速くならない?Sanicなら非同期処理で解決できるかも!

📝 TL;DR (3行要約)

  • 何?: Sanicは、Pythonのモダンな非同期機能 (async/await) をフル活用して作られた、超高速なWebサーバーフレームワークです。
  • いつ使う?: 大量の同時アクセスが予想されるAPIサーバーや、リアルタイム通信が必要なチャットアプリなど、パフォーマンスが最重要視される場面で使われます。
  • 利点: 従来のフレームワークに比べて、I/O処理(例: データベースアクセス、外部API呼び出し)の待ち時間を有効活用し、圧倒的に高速なレスポンスを実現します。

1. 🤔 一体Sanicとは何?(核心的な役割と主な使用例)

PythonでWebアプリを作ろうと思ったとき、多くの人がまずFlaskやDjangoといった有名なフレームワークを思い浮かべるでしょう。これらは非常に素晴らしいツールですが、ある特定の状況、特に「速さ」と「大量の同時接続」が求められる場面では、少し分が悪いことがあります。

そこで登場するのが、今回ご紹介するSanicです!

核心的な役割: スーパー店員によるレジ革命! 🏪

Sanicが何者なのかを理解するために、少し変わったスーパーのレジを想像してみてください。

従来の多くのWebフレームワーク(同期処理)は、非常に真面目な一人の店員さんがレジを担当しているようなものです。 お客さん(リクエスト)が一人来ると、商品のスキャンから袋詰め、お会計まで、そのお客さんの対応が完全に終わるまで、次の人は列に並んでひたすら待たなければなりません。たとえ、店員さんがクレジットカードの承認を待っている数秒間があったとしても、その間、他の人の対応はできません。これが「同期的(Synchronous)」な処理のイメージです。

一方で、Sanic(非同期処理)は、超優秀なスーパー店員さんのような働き方をします。 お客さんAがクレジットカードを端末に差し込み、承認を待っているとします。この「待ち時間」は、店員さんにとっては手持ち無沙汰です。そこでSanic店員は、その数秒の間に、隣で待っているお客さんBの商品のスキャンを始めます。そして、お客さんAのカード承認が終わったら、そちらのレシートを渡す作業に戻ります。

このように、ある処理の「待ち時間」を無駄にせず、その間に他の処理を進めてしまうのが「非同期(Asynchronous)」という考え方です。

Sanicは、Python 3.5から導入された async/await という構文を土台にして、この非同期処理を極めて簡単に、そして効率的に実現するために作られました。特に、データベースへの問い合わせや、外部のWebサイトから情報を取ってくる(APIリクエスト)といった「I/Oバウンド」と呼ばれる待ち時間の多い処理で、その真価を最大限に発揮します。

結果として、Sanicはまるで何人もの店員さんが同時に働いているかのように、たくさんのリクエストを効率よく捌くことができるのです。

主な使用例: Sanicが輝くステージ ✨

この「非同期」という特性を理解すると、Sanicがどのような場面で活躍するのかが自然と見えてきます。

  1. 超高速なAPIサーバーの構築 機械学習モデルの予測結果を返すAPIや、スマートフォンアプリのバックエンドとして、単純なデータをJSON形式で返すAPIサーバーを考えてみましょう。こうしたAPIには、1秒間に何百、何千というリクエストが集中することがあります。Sanicを使えば、リクエストごとに発生するデータベースアクセスなどの待ち時間を有効活用し、サーバーのリソースを最大限に引き出して、大量のリクエストを高速に処理できます。

  2. リアルタイム・アプリケーションの開発 リアルタイムでメッセージを交換するチャットアプリや、株価のように刻一刻と情報が更新されるデータを表示するライブダッシュボードを想像してください。これらのアプリケーションは、サーバーとクライアントが常に接続を保ち(WebSocketという技術を使います)、双方向でデータをやり取りする必要があります。Sanicは非同期アーキテクチャのおかげで、多数のクライアントとの接続を効率的に維持管理するのが得意です。

  3. マイクロサービスアーキテクチャの構成要素として 最近のモダンなシステム開発では、一つの巨大なアプリケーションを作るのではなく、機能ごとに独立した小さなサービス(マイクロサービス)をたくさん作り、それらを連携させる手法がよく取られます。Sanicは、軽量で起動も速く、パフォーマンスも高いため、この一つ一つのマイクロサービスを開発するのに非常に適しています。

このように、Sanicは特に「パフォーマンス」と「スケーラビリティ(拡張性)」が求められる現代的なWebアプリケーション開発において、非常に強力な選択肢となるのです。


2. 💻 インストール方法

Sanicのインストールは、Pythonのパッケージ管理ツールであるpipを使えば一瞬で完了します。ターミナル(WindowsならコマンドプロンプトPowerShell)を開いて、以下のコマンドを実行してください。

pip install "sanic[ext]"

ここで [ext] を付けているのがポイントです。これにより、Sanic本体に加えて、開発を強力にサポートしてくれる公式の拡張機能sanic-ext も一緒にインストールされます。この拡張機能には、リクエストデータの検証(バリデーション)、APIドキュメントの自動生成、CORS対応など、便利な機能がたくさん含まれているので、最初から入れておくことを強くお勧めします。


3. 🛠️ 実際に動作するサンプルコード

百聞は一見に如かず。実際にSanicがどのように動作するのか、以下のコードで体感してみましょう。 このコードを main.py という名前で保存してください。コピー&ペーストしてすぐに実行できます。

import asyncio
from sanic import Sanic, response

# Sanicアプリケーションのインスタンスを作成します。
# アプリケーションに一意の名前を付けます。
app = Sanic("MyFirstSanicApp")

# --- エンドポイントの定義 ---

# 1. 基本的なテキストを返すエンドポイント
@app.get("/")
async def handle_root(request):
    """
    WebサイトのルートURL ("/") にアクセスがあった場合に、
    シンプルなテキストメッセージを返します。
    """
    print("ルートページへのアクセスがありました。")
    return response.text("ようこそ、Sanicの世界へ! 🚀")

# 2. パスパラメータを受け取り、JSONを返すエンドポイント
@app.get("/user/<name>")
async def handle_user(request, name: str):
    """
    URLの一部をパラメータとして受け取り、それを元にJSON形式のデータを返します。
    例: /user/Alice にアクセスすると、 {"user": "Alice", ...} というJSONが返されます。
    """
    print(f"{name}さんのユーザーページへのアクセスがありました。")
    return response.json({
        "user": name,
        "message": f"こんにちは、{name}さん!"
    })

# 3. 非同期処理をシミュレートするエンドポイント
@app.get("/wait")
async def handle_wait(request):
    """
    非同期処理のパワフルさを示すためのデモンストレーション用エンドポイントです。
    この処理は5秒間かかりますが、その間も他のリクエストをブロックしません。
    """
    print("重い処理を開始します... (5秒)")
    # データベースアクセスや外部API呼び出しなどの「待ち時間」が多い処理を模倣しています。
    await asyncio.sleep(5)
    print("重い処理が完了しました!")
    return response.text("5秒間の重い処理が完了しました!お待たせしました。")

# --- サーバーの起動 ---

# このスクリプトが直接実行された場合にのみ、以下のコードが実行されます。
if __name__ == "__main__":
    # Sanicサーバーを起動します。
    # host="0.0.0.0" : 同じネットワーク内の他のPCやスマホからもアクセス可能にします。
    # port=8000      : 8000番ポートで待ち受けます。
    # debug=True     : コードを修正して保存すると、サーバーが自動で再起動するようになります。(開発中に便利)
    app.run(host="0.0.0.0", port=8000, debug=True, auto_reload=True)

このファイルを保存したら、ターミナルでそのファイルがあるディレクトリに移動し、以下のコマンドでサーバーを起動します。

python main.py

ターミナルにSanicのロゴと Server running at http://0.0.0.0:8000 のようなメッセージが表示されれば成功です! それでは、ブラウザを開いて実際にアクセスしてみましょう。

  • http://localhost:8000/
  • http://localhost:8000/user/TaroTaroの部分は好きな名前に変えてみてください)
  • http://localhost:8000/wait

それぞれ異なるレスポンスが返ってくるのが確認できるはずです。


4. 🔍 コードの詳細説明

さて、先ほどのサンプルコードが何をしているのか、一つずつ詳しく見ていきましょう。

① ライブラリのインポートとアプリの初期化

import asyncio
from sanic import Sanic, response

app = Sanic("MyFirstSanicApp")
  • import: まず、プログラムの冒頭で必要な部品を読み込んでいます。asyncioは非同期処理の核となるPythonの標準ライブラリです。sanicからは、アプリケーション本体を作るためのSanicクラスと、レスポンスを生成するためのresponseオブジェクトをインポートしています。
  • app = Sanic(...): これがSanicアプリケーションの「心臓部」を作成する一行です。Sanicクラスのインスタンスappという変数に格納しています。このapp変数を使って、これからURLのルーティング設定などを行っていきます。引数に渡している "MyFirstSanicApp" は、アプリケーションを識別するための名前です。

② ルーティングとハンドラ関数 (@app.get(...)async def)

@app.get("/")
async def handle_root(request):
    # ... (処理) ...
    return response.text("...")
  • @app.get("/"): これはPythonの「デコレータ」と呼ばれる機能です。一言で言えば、「直後の関数に特別な意味付けをする」ためのものです。ここでは、「HTTPのGETメソッドで、/ というURLパスにアクセスがあった場合は、この handle_root 関数を実行してください」という指示をappに登録しています。このようなURLと関数を結びつける仕組みを「ルーティング」と呼びます。
  • async def handle_root(request):: ここがSanicの最大の特徴です。関数が def ではなく async def で始まっていますね。これは「この関数は非同期処理に対応した特別な関数(コルーチン関数)ですよ」という宣言です。Sanicでは、リクエストを処理する関数(ハンドラ関数)は、基本的にこの async def で定義します。
  • request引数: ハンドラ関数は必ず request という引数を第一引数に取ります。この引数には、クライアントからのリクエストに関する情報(ヘッダー、クエリパラメータ、送信されたデータなど)がすべて詰まっています。今回のサンプルでは直接使っていませんが、非常に重要なオブジェクトです。
  • return response.text(...): 関数の最後には、クライアントに返すレスポンスを生成して return します。response.text() はプレーンテキストのレスポンスを、response.json()JSON形式のレスポンスを簡単に作成するためのヘルパー関数です。

③ パスパラメータの利用

@app.get("/user/<name>")
async def handle_user(request, name: str):
    # ...
  • /user/<name>: URLパスの中に <...> という記述をすると、その部分を可変の「パスパラメータ」として受け取ることができます。
  • async def handle_user(request, name: str): デコレータで指定したパラメータ名(この場合は name)を、ハンドラ関数の引数として直接受け取ることができます。name: str のように型ヒントを付けておくと、エディタのサポートも受けやすくなり、コードの可読性も上がります。非常に直感的で分かりやすいですね。

④ 非同期処理のシミュレーションと await

@app.get("/wait")
async def handle_wait(request):
    # ...
    await asyncio.sleep(5)
    # ...
  • await asyncio.sleep(5): この一行が非同期処理の魔法の呪文です。asyncio.sleep(5) は「CPUを全く使わずに、ただ5秒間待つ」という処理です。重要なのは await というキーワード。これは async def の中でしか使えず、「この処理は時間がかかるので、終わるまで一旦ここで処理を中断し、他の仕事(リクエスト処理)があればそちらを先に進めてください」という意味になります。
  • 非同期の威力を体感する方法: ぜひ、ブラウザのタブを2つ開いて試してみてください。
    1. 1つ目のタブで http://localhost:8000/wait にアクセスします。ページが読み込み中のままになるはずです。
    2. すぐに2つ目のタブを開き、http://localhost:8000/ にアクセスします。
    3. どうでしょう?2つ目のタブには「ようこそ、Sanicの世界へ! 🚀」が即座に表示されたはずです! 従来の同期的なフレームワークであれば、waitの処理が終わる5秒間、他のリクエストはすべて待たされてしまいます。しかしSanicは、waitの待ち時間に、他のリクエストをどんどん処理してくれるのです。これが非同期の絶大なパワーです。

⑤ サーバーの起動

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, debug=True, auto_reload=True)
  • if __name__ == "__main__":: これはPythonの決まり文句で、「このファイルが他のファイルからimportされたのではなく、直接 python main.py のように実行された場合にのみ、以下のコードを実行する」という意味です。おまじないだと思って大丈夫です。
  • app.run(...): このメソッドを呼び出すことで、Sanicの開発用サーバーが起動し、リクエストを待ち受ける状態になります。
    • debug=Trueauto_reload=True は開発中に非常に便利です。コードを修正して保存するたびに、自動でサーバーを再起動してくれるので、変更をすぐに確認できます。(注意: 本番環境でdebug=Trueにすることは絶対に避けてください!)

5. ⚠️ 注意点またはヒント

Sanicは非常に強力ですが、そのパワーを正しく引き出すためには、いくつか知っておくべきことがあります。特に初心者が陥りやすい罠と、便利なヒントを一つずつご紹介します。

罠: async の中で同期的な処理を呼び出して、全てを台無しにしてしまう 😱

Sanicの非同期の恩恵は、await を使って「待ち時間」をイベントループに明け渡すことで得られます。しかし、async def の関数の中で、知らずに同期的で重い処理を実行してしまうと、その間イベントループが完全にブロックされ、非同期のメリットが失われてしまいます。

最も典型的な間違いが、Pythonの標準ライブラリ timesleep を使うことです。

悪い例 ❌ (絶対にやらないでください)

import time

@app.get("/bad-sleep")
async def handle_bad_sleep(request):
    print("この処理はサーバー全体を5秒間フリーズさせます...")
    time.sleep(5) # ダメ!これは同期的なスリープです。
    print("やっと終わりました...")
    return response.text("このレスポンスが返るまで、他の誰もサーバーを使えませんでした。")

この time.sleep(5) は、CPUを掴んで5秒間離しません。その間、Sanicは他のどんなリクエストも処理できず、完全に停止してしまいます。

良い例 ✅

import asyncio

@app.get("/good-sleep")
async def handle_good_sleep(request):
    print("この処理は5秒間待ちますが、サーバーは他の仕事を続けられます。")
    await asyncio.sleep(5) # 正解!これは非同期的なスリープです。
    print("5秒経ちました!")
    return response.text("待っている間も、サーバーは忙しく働いていました!")

await asyncio.sleep() を使いましょう。これは「待ち」を適切にイベントループに通知するため、サーバー全体がブロックされることはありません。データベースへのアクセスやHTTPリクエストを行う際も、必ずasyncioに対応した非同期ライブラリ(例: asyncpg, aiohttp)を使うように心がけてください。

ヒント: sanic-extAPIドキュメントを自動生成しよう! 📚

インストール時に sanic-ext を入れましたね。これを使うと、驚くほど簡単にAPIの仕様書(ドキュメント)を自動生成できます。

例えば、サンプルコードの handle_user 関数に、少しだけ情報を追加してみましょう。

from sanic_ext import openapi # この行を追加

# ... (他のコードは同じ) ...

@app.get("/user/<name>")
@openapi.summary("ユーザー情報を取得する") # この行を追加
@openapi.description("指定された名前のユーザー情報と挨拶メッセージを返します。") # この行を追加
@openapi.parameter("name", str, "path", description="ユーザー名") # この行を追加
async def handle_user(request, name: str):
    # ... (関数の内容は同じ)

たったこれだけです!コードを修正してサーバーを再起動し、ブラウザで http://localhost:8000/docs にアクセスしてみてください。

すると、Swagger UIやRedocといった、インタラクティブで見やすいAPIドキュメントが自動的に生成されているはずです!ここから直接APIをテストすることもできます。 チームで開発する際や、将来の自分がコードを見返す際に、このようなドキュメントがあると非常に助かります。sanic-ext は、まさに縁の下の力持ちなのです。


6. 🔗 一緒に見ておくと良いライブラリ

AIOHTTP

Sanicを学んだあなたが次に手を出すべきライブラリは、間違いなく AIOHTTP です。

  • Sanic: 非同期のWeb「サーバー」を作るためのフレームワークでした。
  • AIOHTTP: 非同期のWeb「クライアント」としても、「サーバー」としても使える、非常に強力なライブラリです。

特に「クライアント」としての機能が重要です。あなたのSanicアプリケーションが、他のWebサイトやAPIから情報を取ってくる必要がある場合を考えてみてください。例えば、「ユーザー登録時に、外部の住所検索APIを叩く」といったケースです。

このとき、requests のような従来の同期的ライブラリを使うと、せっかくのSanicの非同期性が台無しになってしまいます。そこで AIOHTTP の出番です。AIOHTTP を使えば、await を使って非同期的に外部へHTTPリクエストを送信できます。

Sanic(サーバーサイド)とAIOHTTP(クライアントサイド)を組み合わせることで、あなたは真の「フルスタック非同期Python開発者」への第一歩を踏み出すことができるでしょう。


7. 🎉 まとめ

今日は、爆速Webフレームワーク Sanic の世界に足を踏み入れてみました。ポイントをもう一度おさらいしましょう。

  • Sanicは、Pythonasync/await を利用した非同期Webフレームワークです。
  • スーパー店員の比喩のように、I/O処理の待ち時間を有効活用することで、驚異的なパフォーマンスを発揮します。
  • ハンドラ関数は async def で定義し、時間のかかる処理は await を使って実行するのが基本です。
  • time.sleep() のような同期的な処理は天敵。必ず asyncio.sleep() のような非同期対応の関数を使いましょう。

Sanicは、Flaskに似たシンプルな書き心地でありながら、Node.jsやGoといった他の高速な言語に匹敵するパフォーマンスをPythonで実現できる、非常に魅力的なフレームワークです。

さあ、理論を学んだら、次は手を動かす番です!

【挑戦課題】

  1. 新しいエンドポイントを追加しよう!: 今回作成した main.py に、@app.get("/info") という新しいエンドポイントを追加してみてください。そのエンドポイントにアクセスすると、あなた自身の名前と好きなプログラミング言語JSON形式で返すようにしてみましょう。(例: {"name": "John Doe", "favorite_language": "Python"}...

  2. 待ち時間をコントロールしよう!: /sleep/<seconds> のように、URLの一部で秒数を指定できる新しいエンドポイントを作ってみましょう。そのエンドポイントでは、指定された秒数だけ await asyncio.sleep(...) してから、「○秒待ちました」と返すように実装してみてください。(ヒント: パスパラメータは最初は「文字列」として受け取られるので、int() を使って整数に変換する必要がありますよ!)

これで、Sanicを使った非同期Webアプリ開発の第一歩は完了です。 このスピード感を武器に、ぜひあなただけの素晴らしいアプリケーションを作ってみてください。

Happy Coding! 🐍✨