投稿

ラベル(Python)が付いた投稿を表示しています

Pythonのメモリ最適化術:ジェネレータとNumPy活用法

Pythonのメモリを究極まで使いこなす:実践的な最適化テクニック Pythonは文法が簡潔で読みやすい言語ですが、メモリの消費効率という点ではC++のような低レベル言語に劣る側面があります。特に大規模なデータ処理や、リソースが制約された環境でPythonを実行する場合、メモリ管理は非常に重要な課題となります。単にコードを書くだけでなく、「メモリを意識した」設計を行うことが、安定性と実行速度を飛躍的に向上させる鍵となります。 本稿では、Pythonの標準機能や外部ライブラリを活用し、メモリフットプリントを劇的に削減するための実用的なテクニックを解説します。 1. 根本的な理解:Pythonメモリの動き 最適化を始める前に、Pythonがどのようにメモリを扱っているのかを大まかに理解しておく必要があります。Pythonは自動メモリ管理(Garbage Collection)を行います。しかし、この仕組みは「参照カウント」と「世代別収集」に依存しており、意図しないメモリリークや過度なメモリ使用が発生する可能性があります。 重要なのは、データの構造とライフサイクルを制御することです。巨大なリストを一度にメモリにロードするのではなく、「必要なときに必要な分だけ生成する」という発想の転換が、最適化の第一歩です。 2. 遅延評価の魔力:ジェネレータの活用 メモリ最適化における最も基本的な、そして最も強力なテクニックの一つが「ジェネレータ(Generator)」の利用です。ジェネレータは、巨大なデータセットをメモリ全体に保持するのではなく、要求されたときに一つずつ値を生成(イテレート)します。これは、遅延評価(Lazy Evaluation)を実現する最もPythonicな方法です。 普通の関数がリスト全体をメモリに格納して返却するのに対し、ジェネレータ関数は yield キーワードを使用します。これにより、データセットのサイズに関係なく、メモリ使用量が非常に小さく保たれます。 以下に、メモリ効率の良いジェネレータの例を示します。 def large_number_generator(n): i = 0 while i このアプローチは、ファイルI/Oやネットワークストリームの処理など、無限または巨大なデータソースを扱う場...

Pythonの並列処理選び方:マルチプロセスvsスレッド完全ガイド

Pythonの性能を限界まで引き出す!並列処理の「選び方」完全ガイド Pythonはシンプルで読みやすい構文から世界中のエンジニアに愛されています。しかし、処理負荷が高くなると、「遅いのではないか」と感じる瞬間が出てきます。特に、何らかの時間を要するタスクが複数ある場合、単一のメインスレッドにすべてを任せるのは効率的ではありません。 ここで登場するのが「並列処理」です。並列処理とは、一つの処理を複数の作業に分割し、同時に実行することで全体のスループットを向上させる手法です。しかし、Pythonには複数の並列処理手法があり、安易に「マルチスレッドを使おう」と決めてしまうと、かえってデバッグやパフォーマンスの低下を招く可能性があります。 この記事では、Python特有の課題であるGIL(Global Interpreter Lock)を理解した上で、あなたが抱えるタスクがCPU集中型なのか、I/O集中型なのかを判別し、最適な並列処理戦略を導き出すための判断基準を解説します。 最初に知っておきたい「GIL」の壁 並列処理の議論を始める前に、Pythonを理解する上で避けて通れない概念があります。それが「GIL(Global Interpreter Lock)」です。これは、CPython(標準のPython実装)において、一度に一つのスレッドしかPythonバイトコードを実行できないようにする仕組みです。 このGILの存在が、マルチスレッドを使っても真の同時実行が難しい、という誤解を生みやすい原因となっています。もしあなたのタスクがCPUをフルに酷使するような計算集約型(CPUバウンド)であれば、GILは性能を制限する最大の要因となるのです。 では、どうすればこの制限を突破できるのでしょうか? それは、タスクの「種類」を見極めることに尽きます。 タスクの分類:CPUバウンドか、I/Oバウンドか? 並列処理の選び方は、あなたの実行したいタスクが「何に時間を使っているか」で決まります。大きく分けて以下の2種類です。 1. CPUバウンド(計算集中型) これは、CPUの計算能力を限界まで使い切ってしまうタスクです。例えば、巨大なデータのソート、画像処理によるピクセルごとの変換、複雑な数値シミュレーションなどが該当します。これらのタス...

Python型ヒント活用術:コード品質と安全性を劇的に高めるベストプラクティス

Pythonのコード品質を劇的に向上させる!型ヒント活用ベストプラクティス 最近、Pythonでの開発において「型ヒント(Type Hinting)」という概念を耳にする機会が増えたのではないでしょうか。以前のPythonは動的型付け言語として知られており、「実行時にエラーが発見される」ことがよくありました。しかし、バージョン3.5以降で導入された型ヒントは、コードに静的な構造と意図を持たせる強力なツールとなりました。 単に def func(x: int) -> str: のように引数に型を記述するだけでは不十分です。真のベストプラクティスとは、その型ヒントを活用して「どれだけメンテナンスしやすく、安全で読みやすいコード」を書けるかにかかっています。 なぜ型ヒントを使うべきなのか?(目的の明確化) 型ヒントを導入する最大のメリットは、「実行時エラー防止」だけでなく、「開発体験(DX)」の向上にあります。主な理由は以下の三点です。 可読性の向上: 型ヒントを見るだけで、この関数がどのようなデータ型の入力を想定し、どのような種類の出力を返すのかが一目瞭然になります。 静的解析による安全性: Mypyのような外部の型チェッカーを使用することで、コードを実際に実行する前に「潜在的な型エラー」を発見できます。これがバグ取りに最も強力な助けとなります。 IDE(統合開発環境)のサポート強化: PyCharmやVS Codeなどのモダンなエディタが型ヒントに基づいて高度な補完機能やリファクタリング提案を行ってくれます。 注意点: 型ヒントは実行時に強制されるわけではありません。これは主に開発者自身と、静的解析ツールのための「メモ書き」のようなものです。しかし、この習慣が積み重なることで、チーム全体のコード品質を引き上げます。 実務で必須の!ベストプラクティス3選 1. 汎用性を意識したGenericsの使用 型ヒントを学ぶ上で最も重要かつ難しいのが、ジェネリック(Generic)な型の使い方です。配列やコンテナクラスを使うとき、「これはどんな型のデータが入っていても良い」と示す必要がある場合があります。 たとえば、リストを受け取り、要素の値をすべて大文字化して返す関数を...

Pythonバッチ処理を高速化する設計パターン3選

Pythonで実現する超高速バッチ処理のための設計パターン 大規模なデータセットを扱うバッチ処理は、システムの根幹を支える重要なタスクです。処理の「高速さ」を追求する際、単にライブラリを切り替えるだけでは不十分です。根本的にプロセスをどう設計するかが鍵となります。本記事では、Python環境で実現可能な、効率的かつ堅牢なバッチ処理の設計パターンを解説します。 1. 基本設計:単なるループ処理からの脱却 多くの初歩的なバッチ処理は、データを読み込み、forループを使って一つずつ処理を進める形になりがちです。しかし、このアプローチはI/O待ちやCPU処理待ちの状態を最大限に活用できておらず、ボトルネックとなりやすいです。設計段階で、並列化と最適化を前提に考える必要があります。 考慮すべき主要なボトルネック I/Oバウンド(読み書きが多い): ディスクアクセスやネットワーク通信がボトルネック。 CPUバウンド(計算が多い): メモリ上の複雑な計算やデータ変換がボトルネック。 メモリバウンド: データセットがあまりにも大きく、メモリ交換(Swapping)が発生する状態。 2. パターン1:並列処理によるスケールアウト (Parallel Processing) 単一のPythonプロセス内で、計算を複数のコアに分割して実行する設計パターンです。PythonのGlobal Interpreter Lock (GIL) の影響を考慮し、適切なライブラリ選定が必要です。 実装の選択肢 データセットを分割し、独立した塊(チャンク)ごとに処理を行うのが基本です。 multiprocessing モジュール: 用途: CPUバウンドなタスクの並列実行。 利点: GILの影響を受けにくく、OSレベルでのプロセス分離が可能です。 注意点: プロセス間のデータ共有(IPC)にオーバーヘッドが発生するため、設計をシンプルに保つことが重要です。 concurrent.futures.ThreadPoolExecutor: 用途: I/...

Pythonロギングのベストプラクティス:本番環境で使える設計ガイド

本番環境でも安心なPythonのロギング設計:ベストプラクティスガイド Pythonでの開発において、ログ(ログファイル)は単なる「動作記録」以上の役割を果たします。それは、アプリケーションが「なぜ動かないのか」「何が起こったのか」を理解するための最も重要な診断ツールです。しかし、多くの開発者が初期段階で print() 関数に頼りがちであり、本番環境での運用に耐えうる、堅牢なロギング設計ができていないという問題があります。 この記事では、単に logging.info() を使うだけでなく、システム全体を俯瞰し、可読性が高く、トラブルシューティングが容易なロギングシステムを構築するためのベストプラクティスをご紹介します。 1. ログを「機能」として扱う 最も重要な原則は、ロギングを「後から気をつけるべき機能」ではなく、「設計の初期段階から組み込む必須のインフラストラクチャ」として扱うことです。ロギングの設定(Configuration)は、アプリケーションのコードとは分離されているべきです。 おすすめの方法は、設定ファイルを読み込むか、あるいは専用の初期化関数( setup_logging() など)を用意して、アプリケーション起動時に一度だけ呼び出すことです。 2. ログレベルの徹底理解と適切な利用 logging モジュールが提供するログレベル(DEBUG, INFO, WARNING, ERROR, CRITICAL)は、単なる分類記号ではありません。それぞれのレベルには「この情報が何を意味するか」という明確な定義があります。使い分けを徹底することがログの価値を最大化します。 DEBUG: 詳細なデバッグ情報。通常、本番環境では無効化します。(例:ループ処理の内部変数の値) INFO: アプリケーションが正常に実行された主要なステップ。システムの状態変化を追跡します。(例:ユーザーがログインした、処理を開始した) WARNING: 潜在的な問題や、仕様上問題ではないが注意が必要な状況。即座の障害ではないが、監視が必要なサインです。(例:設定ファイルが見つからないが、デフォルト値を使用) ERROR: 特定の処理が失敗したが、アプリケーション全体は動作を続けることができるレベルのエラー。(例:データベースへの書き...

Python レートリミッター実装

Pythonで実装するレートリミッター Pythonで実装するレートリミッター レートリミッターは、特定の期間内に特定のユーザーやIPアドレスからのリクエスト数を制限する仕組みです。これは、APIの過負荷を防ぎ、悪意のある攻撃(DDoS攻撃など)から保護するために非常に重要です。本記事では、Pythonで簡単なレートリミッターを実装する方法を紹介します。 レートリミッターの基本的な考え方 レートリミッターは、通常、以下の要素で構成されます。 制限数 (Limit): 一定期間内に許可されるリクエストの最大数 時間間隔 (Interval): リクエストが許可される間隔の長さ(通常は秒単位) カウンター (Counter): 許可されたリクエストの数を追跡する変数 これらの要素に基づいて、現在のリクエストが制限を超えているかどうかを判断し、超過している場合はリクエストを拒否します。 Pythonによるレートリミッターの実装例 以下に、Pythonでレートリミッターを実装する簡単な例を示します。 import time class RateLimiter: def __init__(self, limit, interval): self.limit = limit self.interval = interval self.request_times = {} # ユーザーまたはIPアドレスに基づいてリクエスト時刻を記録 def is_allowed(self, identifier): now = time.time() if identifier not in self.request_times: self.request_times[identifier] = [] # リクエスト時刻を最新の状態に更新 self.request_times[identifier].append(now) self.request_times[identifier].sort() # 最後のリクエストがinter...

Python保守戦略:長期的なコード管理

Pythonコードを長期保守するための構成戦略 Pythonコードを長期保守するための構成戦略 Pythonのコードを長い期間にわたって維持していくためには、単に書くだけでは不十分です。適切な構成戦略を立てることで、保守作業の効率を高め、バグの発生を減らし、将来的な変更にも柔軟に対応できるようになります。本記事では、長期保守に焦点を当てたPythonコード構成戦略について解説します。 1. モジュール化の徹底 Pythonコードは、機能ごとにモジュールに分割することが重要です。単一の大きなファイルではなく、小さな独立したモジュールを作成することで、コードの可読性、再利用性、テスト容易性が向上します。モジュール間の依存関係を明確にし、グローバルな名前空間を最小限に抑えるように心がけましょう。 # 例: # user.py def register_user(username, password): # ユーザー登録処理 pass # product.py def get_product_details(product_id): # 製品詳細取得処理 pass 2. パッケージング 関連するモジュールをまとめてパッケージにすることで、コードの組織化をさらに強化できます。パッケージは、モジュールをグループ化し、名前空間を提供することで、コードの衝突を回避し、管理を容易にします。Pythonのパッケージングは、ディレクトリ構造と __init__.py ファイルを使用します。 # my_project/ # user/ # __init__.py # user.py # product/ # __init__.py # product.py 3. テスト駆動開発 (TDD) の導入 TDDは、テストコードを先に書き、そのテストに合格するようにコードを実装する手法です。TDDを導入することで、コードの品質を向上させ、バグの早期発見が可能になります。単体テスト、結合テスト、システムテストなど、様々なレベルのテストを実施することが推奨されます。 例えば、`user.py` モジュー...

Python ガベージコレクション完全ガイド

Python のガベージコレクション (GC) を正しく理解する Python のガベージコレクション (GC) を正しく理解する Python は、メモリ管理に関して他のプログラミング言語とは異なる特徴を持っています。その中でも特に重要なのがガベージコレクション (GC) です。このブログ記事では、Python の GC について深く掘り下げ、その仕組みと使い方を解説します。 ガベージコレクションとは何か? ガベージコレクションは、プログラムで明示的にメモリの解放を行わなくても、不要になったメモリ領域を自動的に回収する機能です。C++ や Java のような言語では、プログラマが手動でメモリを割り当てたり解放したりする必要がありますが、Python では GC がその役割を担います。 Python の GC の種類 Python には主に以下の 2 つの種類の GC が存在します。 1. トラッキング (Reference Counting) これは最も基本的な GC メカニズムです。各オブジェクトには「参照カウント」という変数があり、そのオブジェクトを参照しているものの数を保持しています。オブジェクトへの参照がなくなる(つまり、参照カウンタが 0 になる)と、GC はそのオブジェクトをすぐに回収します。 class MyObject: def __del__(self): print("オブジェクトを削除しました") obj1 = MyObject() obj2 = obj1 # 参照カウントが 2 になる del obj1 # 参照カウントが 1 になる del obj2 # 参照カウントが 0 になり、オブジェクトが削除される 2. マークとスイープ (Mark and Sweep) トラッキングだけでは解決できない問題があります。例えば、オブジェクトが別のオブジェクトへの参照を保持している場合(循環参照)、トラッキングではそのオブジェクトは回収されません。 マークとスイープは、この問題を解決するために使用されます。 マーク (Mark): GC は、まずオブジェクトグラフのルートオブジェクト (グローバル変数など) から...

Pythonメモリリーク対策

Python でのメモリリーク調査方法 Python でのメモリリーク調査方法 Python プログラムにおけるメモリリークは、アプリケーションのパフォーマンス低下や、最終的にはクラッシュを引き起こす可能性があります。メモリリークとは、プログラムが不要になったメモリを解放せずに保持し続ける現象を指します。本記事では、Python でメモリリークを調査し、解決するための効果的な方法をいくつか紹介します。 メモリリークの原因 Python のメモリリークは、主に以下の原因によって発生します。 循環参照: オブジェクト同士が互いに参照し合っている場合に、参照を解除することができなくなることがあります。 グローバル変数の不適切な使用: グローバル変数が長期間保持され、オブジェクトのライフサイクルよりも長く生き続けると、メモリリークの原因となります。 不要なオブジェクトの保持: プログラムのどこかでオブジェクトが生成され、その後使用されなくなっても、参照が削除されないままになっている場合。 リソースの解放忘れ: ファイルハンドルやネットワーク接続などのリソースが正しく解放されず、プログラム終了時に解放されない。 メモリリークの調査方法 Python でメモリリークを調査するためには、いくつかのツールとテクニックを利用します。 1. 組み込みのツール: `objgraph` と `memory_profiler` まず、 objgraph モジュールはオブジェクト間の参照グラフを作成し、どのオブジェクトが他のオブジェクトを参照しているかを視覚的に把握するのに役立ちます。これは、循環参照を見つける上で非常に重要です。 import objgraph import time # ここでメモリリークが発生するようなコードを実行する # 例えば、循環参照を発生させるようなオブジェクトを作成するなど # ... objgraph.show_graph() 次に、 memory_profiler は、コードの特定の範囲のメモリ使用量を追跡します。これにより、どの関数やオブジェクトが最も多くのメモリを消費しているかを特定できます。 from memory_profiler import profi...

Python設定ファイル管理設計

Pythonにおける設定ファイル管理の設計 Pythonにおける設定ファイル管理の設計 Pythonアプリケーションにおける設定管理は、柔軟性、保守性、そしてアプリケーションのさまざまな環境への適応性を確保するために非常に重要です。本稿では、Pythonプロジェクトで設定ファイルを効率的に管理するための設計パターン、ベストプラクティス、および考慮すべき事項について解説します。 設定ファイルの選択 Pythonには、設定ファイルを扱うための様々な方法があります。最も一般的なものをいくつか紹介します。 .ini ファイル: シンプルなキー-バリュー形式で、設定の読み書きが容易です。特に小規模なアプリケーションに適しています。 JSON ファイル: Pythonの`json`モジュールを使って読み書きできます。柔軟性が高く、複雑な設定構造を表現できます。 YAML ファイル: 読み書き時に`PyYAML`ライブラリを使用します。人間にとって読みやすく、設定ファイルの記述に適しています。 環境変数: サーバーやクラウド環境で設定を管理するのに適しています。 設定ファイルの構造 設定ファイルは、アプリケーションの構成要素をセクションごとに分割して整理することが推奨されます。例えば、データベース接続情報、APIキー、アプリケーションの動作モードなどをそれぞれセクションに分けることができます。 # 例: settings.ini ファイル [Database] host = localhost port = 5432 user = myuser password = mypassword database = mydb [API] key = your_api_key endpoint = https://api.example.com この例では、データベース接続情報とAPIキーがそれぞれセクションで定義されています。 設定ファイルの読み込みと利用 Pythonで設定ファイルを読み込むには、通常、`configparser`モジュール(.iniファイル用)、`json`モジュール、`PyYAML`ライブラリなどを使用します。設定を読み込んだ後、`dict`型として利用できます。 ...

Python 外部コマンド 安全な実行方法

Pythonで安全に外部コマンドを実行する方法 Pythonで安全に外部コマンドを実行する方法 Pythonで外部コマンドを実行することは、システム管理、自動化、そして特定のタスクを実行する際に非常に便利です。しかし、外部コマンドの実行には、セキュリティ上のリスクが伴います。悪意のあるコマンドが実行されたり、予期せぬエラーが発生したりする可能性があります。この記事では、Pythonで外部コマンドを安全に実行するためのベストプラクティスについて解説します。 リスクを理解する 外部コマンドを実行する際に潜在的に存在するリスクを理解することが重要です。主なリスクは以下の通りです。 コマンドインジェクション: ユーザーからの入力がコマンドの一部として使用される場合、攻撃者が悪意のあるコマンドを注入する可能性があります。 権限の問題: スクリプトが実行されるユーザーアカウントが、外部コマンドを実行するのに必要な権限を持っていない場合、エラーが発生したり、予期しない動作を引き起こしたりする可能性があります。 エラー処理の欠如: 外部コマンドがエラーを返した場合、スクリプトが適切にエラーを処理しないと、システムが不安定になる可能性があります。 安全な実行方法 Pythonで外部コマンドを安全に実行するためには、以下の方法を検討してください。 1. 常にシェルを介して実行する 直接コマンドを呼び出すのではなく、`subprocess`モジュールを使用してシェルを介してコマンドを実行します。これにより、コマンドの構文解析とセキュリティチェックが実行されます。 import subprocess # コマンドのリスト command = ['ls', '-l'] # コマンドを実行 process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() # 出力をデコード stdout = stdout.decode('utf-8') stderr = stderr.decode(...

データクラスと型ヒントで安全なPython

データクラスと型ヒントで安全なPythonを書く データクラスと型ヒントで安全なPythonを書く Pythonのコードを書く上で、タイプミスや型に関する問題を未然に防ぐことは非常に重要です。近年、Pythonではデータクラスと型ヒントという強力なツールが生まれ、コードの安全性を高めることができます。この記事では、これらのツールをどのように活用すれば、より安全でメンテナンスしやすいコードを書けるのかを解説します。 データクラスとは? データクラスとは、Pythonでデータを持つクラスを簡単に定義するための特殊なクラスです。これを使うと、多くのデータクラスで共通して使用される属性(名前、年齢、住所など)を自動的に生成してくれます。また、これらの属性にアクセスするための便利なメソッドも自動的に生成されます。 from dataclasses import dataclass @dataclass class Person: name: str age: int city: str person = Person("太郎", 30, "東京") print(person.name) print(person.age) print(person.city) この例では、`Person`というデータクラスを定義しています。`name`, `age`, `city`という属性を定義し、それぞれの属性の型(str, int, str)も指定しています。型ヒントは、型チェックやドキュメント生成に役立ちます。 型ヒントとは? 型ヒントとは、Pythonの変数、関数の引数、戻り値に型を指定するためのものです。これは必須ではありませんが、型ヒントを使うことで、コードの可読性を高め、型チェックツール(mypyなど)を使って、潜在的な型エラーを早期に発見することができます。 def greet(name: str) -> str: return "Hello, " + name print(greet("John")) この例では、`greet`という関数を定義しています。この関数の引数`name`は`str...

AI異常検知システム構築ガイド

AIを用いた異常検知システムの作り方 AIを用いた異常検知システムの作り方 近年、様々な分野でデータの異常を検知する技術が重要視されています。ここでは、Pythonと機械学習ライブラリを使って、AIを用いた異常検知システムを構築する方法を解説します。このシステムは、大量のデータから逸脱したパターンを自動的に検出し、アラートを発することで、異常事態への早期対応を可能にします。 1. 異常検知の概要 異常検知とは、通常とは異なるデータポイントやパターンを自動的に識別する技術です。これは、製造業における機械の故障検知、金融業界における不正取引の検出、ネットワークセキュリティにおけるサイバー攻撃の検知など、幅広い分野で活用されています。本システムの目的は、時系列データにおける異常を検知することに焦点を当てます。 2. 必要な環境 このシステムを構築するために、以下の環境が必要です。 Python 3.6 以上 NumPy Pandas Scikit-learn これらのライブラリは、pipを使ってインストールできます。 pip install numpy pandas scikit-learn 3. データ準備 異常検知システムの性能は、使用するデータに大きく左右されます。ここでは、単純な例として、ランダムな数値データを使って異常検知システムを構築します。 データ生成には、PandasのDataFrameを使用します。例えば、以下のようなデータセットを生成します。 import pandas as pd import numpy as np np.random.seed(0) data = np.random.randn(100) df = pd.DataFrame({'value': data}) print(df.head()) このデータは、通常は値が0を中心に分布しているはずです。 4. 異常検知モデルの構築 ここでは、Isolation Forestという異常検知アルゴリズムを使用します。Isolation Forestは、データをランダムに分割していくことで、正常なデータよりも異常なデータの方が早く分離できるという性質を利用しています。 fro...

最新Python Tips:活用術とライブラリ

最新の Python に関する Tips 最新の Python に関する Tips Python は常に進化しており、新しい機能やライブラリが続々と登場しています。ここでは、最近リリースされたものや、コミュニティで注目されているいくつかの便利な Tips をご紹介します。 1. 型ヒント(Type Hints)の活用 Python 3.5 で導入された型ヒントは、コードの可読性と保守性を向上させる強力なツールです。型ヒントを使用することで、コードの実行前に型チェックを行うことができます。これは、エラーの早期発見に役立ち、開発効率を向上させます。 def greet(name: str) -> str: return "Hello, " + name この例では、`greet` 関数が文字列型の引数 `name` を受け取り、文字列型の値を返すと定義しています。 `typing` モジュールを使って、より複雑な型も定義できます。 2. async/await の理解と活用 async/await は、非同期プログラミングを簡潔に記述するための構文です。これにより、I/O バウンドな処理(ネットワークアクセス、ファイル操作など)を効率的に処理できます。 特に、Web アプリケーションや API の開発において、パフォーマンスを向上させるために重要なスキルです。 import asyncio async def fetch_data(url): # ... (ネットワークリクエスト処理) ... return data async def main(): result = await fetch_data("https://example.com") print(result) asyncio.run(main()) `async` キーワードは関数が非同期であることを示し、`awai...