
2026/09/02 3:44
Python の非同期化に取り組んでいます
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Python 3.15a7 は PEP 810 を導入し、
lazy import <module> のような構文や、後方互換性のリスト宣言(__lazy_modules__ = [...])を通じて遅延インポートを可能にします。これにより、未使用のモジュールはアクセスされるまで読み込まれず、CLI ツールのパフォーマンスが劇的に向上します;ベンチマークでは --help などの単純なコマンドで最大 2 倍の高速化が確認され、repo-review(3 倍)や cibuildwheel ではさらに大きな改善が見られます。環境フラグ(-X lazy_imports=all、PYTHON_LAZY_IMPORTS=all)を使用してすべてのインポートを遅延とみなすことも可能ですが、これは主にテスト目的です。すべてのモジュールを遅延としてマークすべきではありません;インポート時またはトップレベルアクセスに副作用があるもの(例:re.compile)はエラーを避けるため、積極的ロードされたままにしておく必要があります。flake8-lazy ツールは、欠落した遅延宣言を発見し、Python 3.15+ の構文に対して __lazy_modules__ リストを検証するのを支援します;使用されるエラーコードには、LZY1xx(宣言の不足)、LZY2xx(リスト検証)、LZY3xx(ネイティブキーワードとの競合)、LZY4xx(セマンティック安全性チェック)があります。AI ツールを使用して flake8-lazy が開発され、開発者は手動で 5% 未満のコードしか書かず、AGENTS.md のようなパターンのサポートを受けています。高度な使用法には、自動修正(uvx flake8-lazy --apply=list)および --format=lazy-modules 出力を消費する AI モデルが含まれます。追加の最適化には、TYPE_CHECKING = False を使用して TYPE_CHECKING を避けることがあり、これは Ruff チェック(TID251)がサポートするパターンです。制限事項には、相対インポートに関する曖昧さ(a.b 対型)および CPython 3.15 ワheel でまだコンパイルされていないライブラリとの互換性の問題が含まれます。将来の改善は、サードパーティライブラリが遅延インポートを採用するにつれて期待されており、hyperfine と -X importtime を使用して測定されます。本文
Python 3.15 と AI を活用した「怠惰型インポート」の劇的パフォーマンス向上
Python 3.15a7 では、
uv を用いたインストール環境においてついに**「怠惰型インポート(Lazy Imports)」**機能を実装しました。この PEP 810 で提案された画期的な機能は、CLI アプリケーション(特に --help などを使用する際)の起動時間を劇的に高速化します。大量のインポートが必要で大規模コードであっても、以前失敗した試みとは異なり、今回はライブラリ側の対応が必要です。
以下に「怠惰型インポートとは」「活用方法」「ツール紹介」、そして「AI を多用して開発した体験」をまとめました。
まとめ(TL;DR):
uvx flake8-lazy --apply=listを実行し、Python 3.15 でコードを魔法のように高速化しましょう!
怠惰型インポートとは?
標準的な Python の CLI ファイルでは、コマンド引数を解析する前にすべての依存モジュールがインポートされることがあります。
従来の問題点
argparse や numpy をインポートしただけでも処理が発生します。
import argparse import numpy def main(): parser = argparse.ArgumentParser() parser.add_argument("--foo", action="store_true") args = parser.parse_args() if args.foo: print(numpy.array([1, 2, 3])) # ここまで処理しない限り numpy は不要
フラグ実行時:--help
のインポートが発生しますが、実際のロジックには関与しません。numpy
環境での影響:uv
はデフォルトでバイトコードの事前コンパイルを行いません(明示的指示がない場合)。これによりインストールは速くても、最初のインポート処理が遅延してしまいます。uv
この問題はパッケージ化されたライブラリでも同様です。
# __init__.py from . import a from . import b __all__ = ["a", "b"]
だけで利用できるようにしたいが、import lib
を使う必要がある場合。lib.a.stuff- すべてのインポートされたモジュールを使わなくても、コストは発生してしまいます。
は明示的なインポートを推奨する一方、古いライブラリではこのパターンが一般的です。rich
解決策:怠惰型インポート(Python 3.15)
Python 3.15 では、以下のように記述できるようになりました。
lazy import argparse lazy import numpy def main(): parser = argparse.ArgumentParser() # ...
- 動作: インポート時に何の処理も行われません。モジュール自体もインストールされていなくても問題ありません。
- 活性化: 初めてそのオブジェクトを使用するタイミングで、真にインポートされたオブジェクトへと変換されます。
を実行した場合は--help
のインポートすら行われず、劇的に高速化します。numpy
後方互換性のある構文
古い Python バージョンでも動作する別の方法もあります。
__lazy_modules__ = ["argparse", "numpy"] import argparse import numpy
- これにより古いバージョンでの動作も保証されます(ただし、単に「怠惰型」ではない点に注意)。
- リンター(例:
)もこの配置を許容し、違反を検出しないよう更新されています。Ruff
補足: Python には全インポートを怠惰型にするフラグ
-X lazy_imports=all もありますが、主にテスト用途で使用されます。
なぜすべてのモジュールを怠惰型にできないのか?
一部のモジュールはインポート時に副作用(初期化処理など)を持つためです。この副作用が直後に発生する必要がある場合は怠惰型化できません。
怠惰型化できないパターン
エラーハンドリングが必要な場合などは、単純な
lazy import は機能しません。
try: import numpy except ModuleNotFoundError: ... # ここに処理を移す必要があります
代替案:部分的に怠惰なアプローチ
import importlib.util if importlib.util.find_spec("numpy") is None: ... # 不足している場合の処理 lazy import numpy
- 欠点: インポートを行わない場合よりわずかにコストがかかります。
- 注意点: サブパッケージへのアクセス時に、トップレベルのインポートが発生します。また、エラーメッセージの内容も限定的です。
定数やトップレベルの使用
ファイル処理などトップレベルで常に必要とするライブラリ(例:
re)は怠惰型にする意味がありません。ただし、キャッシュを活用することで回避できます。
import functools lazy import re @functools.cache def regex() -> re.Pattern: return re.compile(...) # 必要な時にのみインポート・コンパイル
この機能を使うには
from __future__ import annotations は必須ではありません(Python 3.14 より注釈がデフォルトで怠惰型化するため)。ただし、エラーを意図的に先送りするだけの場合は推奨されません。
ファイル全体を怠惰型にする場合
class AllLazy: @staticmethod def __contains__(_: str) -> bool: return True __lazy_modules__ = AllLazy()
flake8-lazy: 管理と最適化のためのツール
ライブラリが
__lazy_modules__ を追加するには複雑な考慮が必要です。そこで開発されたヘルパーツールです。flake8-lazy は、正確に何を追加すべきかを判断し、コードを整理します。
使用方法
| コマンド | 説明 |
|---|---|
| エラー表示: 怠惰型宣言の不足などを報告します。 |
| 提案表示: 追加すべき行を表示します。 |
| 自動修正: リスト形式で即座にコードを修正します。 |
# pipx を使用する場合も同様 pipx run flake8-lazy --apply=list <filenames>
エラーコードの概要
- Code 1xx: 怠惰型宣言の欠如
- トップレベルで使用されていない標準ライブラリやサードパーティライブラリがリストされていない場合。
- Code 2xx:
の検証__lazy_modules__- ソート未実施、重複、割り当て順序の誤り、絶対名指定など。
- Code 3xx: ネイティブ怠惰型キーワード (Python 3.15+)
- Python 3.15 以降の新構文特有の問題を検出。
- Code 4xx: セマンティクスと安全性
- トップレベルでアクセスされるのに怠惰型化されている、パッケージ固有のルール違反など。
成果:劇的な速度向上
自身のソースコードや主要プロジェクトに適用した結果、
--help フラグを含む処理が大幅に高速化されました。
| パッケージ | Before (ms) | After (ms) | Speedup | 備考 |
|---|---|---|---|---|
| flake8-lazy | 100+ ms | 50 ms | 2x | オリジナル版の向上 |
| repo-review | 113 ms | 35 ms | 3x | PR 提出物 |
| cibuildwheel | 179 ms | 61 ms | 3x | PR 提出物 |
| build | 50 ms | 45 ms | 10% | もともと高速のため変化较小 |
| packaging | N/A | N/A | N/A | なし |
- M1 マシンでの Python 起動: 約 15 ミリ秒。これより速くすることはできません(標準ライブラリのロードなど)。
- 測定ツール:
やhyperfine
を使用して詳細な計測が可能です。-X importtime - 将来性: サードパーティライブラリも怠惰型インポートを追加すれば、さらに改善が期待されます。
課題:
- エッジケース(例:
のタイプヒント解析など)で正しく動作しない場合があります。不足している処理を過剰に補うより、慎重に対応すべきです。dataclasses - 一部のモジュール(
など)はまだ CPython 3.15 向けホイールが正式リリースされておらず、最大の効果が確認できない段階です。numpy
AI を多用した開発体験
このプロジェクトは、AI ツールを多大に活用した最初の成功事例の一つです(保守支援の
plumbum 以降)。
開発プロセスと成果
- 速度: 手作業の場合の数日かかる作業を、約 1 日で完成しました。実装の詳細を追跡するのではなく、指示に基づき動作を確認・修正するスタイルでした。
- 品質: コードは少し長くなる傾向(重複除去)がありますが、全体としてクリーンで人間らしく見えます。
- リファクタリング: エージェントはプロンプトなしでテスト生成やエラー処理、マージ競合の解決などを完璧に行いました。
技術スタックと構成
AI の成功には、強力なセットアップが寄与しました。
- テンプレート: Scientific Python Development Guide をベース。
- ツール:
,uv
,prek
の自動利用設定(nox
など)。AGENTS.md - リンター:
(TID251 チェックなど)。Ruff - LLM モデル: GPT-5.3-codex が主体で、Claude Sonnet 4.6 が選択される場合あり。
AI の具体的な活躍例
- コード修正: エラーメッセージの改善や、例外注釈のバージョン適合(Python 3.11 ゲートなど)。
- ドキュメント管理: README とドキュメント間の重複解消。
- 複雑なリファクタ: 長い
の再設計(__init__.py
の再適用含む)を完璧に実行。__lazy_modules__ - テスト作成: プロンプトなしで追加された機能に対するテストコードを生成。
結論:AI と人間の役割分担
- 人間: 要件定義、高レベルなアーキテクチャ決定(ライブラリ設計など)、最終的な品質判断。
- AI: 実装の詳細作業、繰り返し処理、テスト作成、ループ処理の高速化。
このように「待つ」ことさえできれば、開発スピードは手書きの5〜7 倍に近づく可能性があります。適切なリンティングとテスト設定があれば、AI は非常に強力なヘルパーになります。