鈴木たかのり
Python 3.
lazy imports(遅延インポート)とは
lazy importsはPEP 810で提案された機能で、Python 3.
import文に新しいソフトキーワードlazyを付加することにより、遅延インポートとなります。
lazy import json
lazy from json import dumps
遅延インポートされたモジュールは、そのモジュールが使用されるまでは読み込まれません。通常のインポート文はimport jsonと書かれた行をPythonが実行した段階で、モジュールが読み込まれて実行されます。
遅延インポートを使用することにより、Pythonプログラムの起動時間、メモリ使用量や不要な処理を削減することを目的としています。この機能が有効な例としてはコマンドラインツール、テスト、依存関係が複雑なアプリケーションが上げられています。
通常のインポート文は変更せず、lazyソフトキーワードの付いた遅延インポートのみで有効となるため、下位互換性が維持されています。
lazy importsのパフォーマンスを確認する
簡単なサンプルコードでlazy importsのパフォーマンスを確認します。以下の簡単なPythonスクリプトを用意します。このコマンドはjsonモジュールとdatetimeモジュールをインポートしています。
import argparse
import datetime
import json
def main():
parser = argparse.ArgumentParser(description="lazy imports sample program")
parser.add_argument('filename')
parser.add_argument('-c', '--count')
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
with open(args.filename) as f:
data = json.load(f)
print(data)
print(datetime.datetime.now())
if __name__ == "__main__":
main()
もう1つのPythonスクリプトは同様のコードですが、2つのモジュールのインポート文がlazy importになっています。
import argparse
lazy import datetime # lazy import
lazy import json # lazy import
def main():
parser = argparse.ArgumentParser(description="lazy imports sample program")
parser.add_argument('filename')
parser.add_argument('-c', '--count')
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
with open(args.filename) as f:
data = json.load(f)
print(data)
print(datetime.datetime.now())
if __name__ == "__main__":
main()
この2つのスクリプトを--helpオプションを指定して呼び出してみます。1回だと処理時間に大きな差がないので100回実行します。--helpオプションを指定するとこのスクリプトはargparseがヘルプメッセージを出力して、そこで終了します。そのため、cli_
結果は以下の通りで、cli_
% time seq 100 | xargs -I {} python3.15 cli.py --help > /dev/null
2.06s user 0.45s system 90% cpu 2.780 total
% time seq 100 | xargs -I {} python3.15 cli_lazy.py --help > /dev/null
2.00s user 0.43s system 90% cpu 2.667 total
lazy importsの仕組み
このlazy importsはどのように動作しているのでしょうか? lazyソフトキーワードのついたインポート文を実行すると、モジュールは読み込まれずに遅延プロキシオブジェクトが作成され、そのオブジェクトがモジュール名と関連付けられます。そして、モジュールが使用されるタイミングでプロキシオブジェクト経由でモジュールが読み込まれます。
以下は対話モードでlazy importを実行した例です。
>>> import sys
>>> lazy import json # lazy importを実行
>>> 'json' in sys.modules # モジュール一覧にjsonが存在しない
False
>>> result = json.dumps({"hello": "world"}) # jsonモジュールの関数を実行
>>> 'json' in sys.modules # モジュール一覧にjsonが存在する
True
lazy importsの言語仕様
lazyソフトキーワードはどこでも使用できるわけではありません。グローバルレベルでしか使用できないため、以下のような書き方は構文エラーとなります。また、import *とfrom __文でもlazyソフトキーワードは使用できません。
lazyソフトキーワードを使用できないパターン# SyntaxError: lazy import not allowed inside functions
# シンタックスエラー:関数の中でlazy importはできない
def foo():
lazy import json
# SyntaxError: lazy import not allowed inside classes
# シンタックスエラー:クラスの中でlazy importはできない
class Bar:
lazy import json
# SyntaxError: lazy import not allowed inside try/except blocks
# シンタックスエラー:try/exceptの中でlazy importはできない
try:
lazy import json
except ImportError:
pass
# SyntaxError: lazy from ... import * is not allowed
# シンタックスエラー:lazy from ... import * とは書けない
lazy from json import *
# SyntaxError: lazy from __future__ import is not allowed
# シンタックスエラー:lazy from __future__ importとは書けない
lazy from __future__ import annotations
__lazy_modules__ を使用してPython 3.14でも動作させる
lazy importはPython 3.
% python3.14 cli_lazy.py
File ".../cli_lazy.py", line 2
lazy import datetime # lazy import
^^^^^^
SyntaxError: invalid syntax
Python 3.__という変数を定義し、その中にlazy importの対象となるモジュールの名前を列挙します。以下のように書くと
__lazy_modules__を使用した例__lazy_modules__ = ["datetime", "json"]
import argparse
import datetime
import json
def main():
parser = argparse.ArgumentParser(description="lazy imports sample program")
parser.add_argument('filename')
各バージョンのPythonで動作することが確認できます。
% python3.14 cli_lazy_modules.py --help usage: cli_lazy_modules.py [-h] [-c COUNT] [-v] filename ... % python3.15 cli_lazy_modules.py --help usage: cli_lazy_modules.py [-h] [-c COUNT] [-v] filename ...
コードを書き換えずにlazy importを有効にする
既存のコードを書き換えずにPythonプログラム実行時にlazy importを有効にする方法があります。それは-X lazy_オプションと、PYTHON_環境変数です。このオプションと環境変数はどちらもallまたはnormalという値を指定できます。normalはデフォルト値で、lazyソフトキーワードが存在するとlazy importします。allを指定すると、すべてのインポート文のデフォルト動作がlazy importとなります。
また、実行中のプログラムに対してsys.関数とsys.関数によって、この値を設定、取得できます。
実体をインポートするときにエラーが発生する
次のコード例は関数名をtypo
lazy from json import dumsp # dumpsをtypoしている
print("アプリケーションは正常に開始")
print("データを処理する...")
# 関数を最初に使用するときにエラーが発生する
result = dumsp({"key": "value"})
このようなプログラムを実行した場合、今まではimport文でエラーが発生していましたが、lazy importでは実体をインポートするときにエラーが発生します。2つのprint()関数は正常に動作し、そのあとの7行目のtypoしたdumsp()関数を実行するときにインポートエラーが発生しています。エラーメッセージではlazy importで遅延インポートするときに例外が発生し、その結果として関数の実行でエラーとなった、という内容になっています。
% python3.15 typo.py
アプリケーションは正常に開始
データを処理する...
Traceback (most recent call last):
File ".../typo.py", line 1, in <module>
lazy from json import dumsp # sumpsをtypoしている
ImportError: lazy import of 'json.dumsp' raised an exception during resolution
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".../typo.py", line 7, in <module>
result = dumsp({"key": "value"})
^^^^^
ImportError: cannot import name 'dumsp' from 'json' (.../python3.15/json/__init__.py).
Did you mean: 'dump'?
まとめ
Python 3.
ぜひ試してみてください。
