Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ModuleNotFoundErrorの傾向と対策:仕組みから学ぶImport / Unpacking ModuleNotFoundError

ModuleNotFoundErrorの傾向と対策:仕組みから学ぶImport / Unpacking ModuleNotFoundError

PyCon APAC 2023 登壇資料
https://2023-apac.pycon.jp/timetable?id=9TKP3P

Toshifumi Tsutsumi

October 27, 2023
Tweet

More Decks by Toshifumi Tsutsumi

Other Decks in Programming

Transcript

  1. #pyconapac_5 堤 利史 Toshifumi Tsutsumi @tosh2230 Data Engineer GMO Pepabo,

    inc. 風来のシレン 最新作を待ちわびています 2 自己紹介
  2. #pyconapac_5 表記例 🖋 9 # これはコメント # 先頭が $ はコマンド

    $ bash # 先頭が >>> は Python インタプリタ >>> import os # 先頭に何もなければ Python モジュールのコード import os
  3. #pyconapac_5 モジュールとは 15 - 定義や文をファイルに保存、再利用できるようにしたもの - 拡張子が .py のファイル -

    Python コードの組織単位として機能する - モジュールに含まれるものの例 - 関数 - クラス - 変数
  4. #pyconapac_5 - 複数のモジュールやサブパッケージを “ドット付きモジュール名”で構造化したもの - foo.bar は、 foo パッケージの モジュール

    bar - 多くのパッケージが PyPI; Python Package Index で公開されている - パッケージには2種類ある。今回は前者を紹介 - Regular packages - Namespace packages パッケージとは 16
  5. #pyconapac_5 Regular packages 17 - __init__.py を含む単一のディレクトリ - 読み込まれるときに __init__.py

    が実行される - パッケージの初期化 - ディレクトリとパッケージが対応しているので わかりやすい
  6. #pyconapac_5 Regular packages 18 $ tree ./greetings/friends -I __pycache__ ./greetings/friends

    ├── __init__.py ├── cat.py ├── dog.py └── human.py greetings friends cat dog human
  7. #pyconapac_5 greetings friends # greetings.friends パッケージから # cat モジュールをインポート from

    greetings.friends import cat Import の例 20 cat dog human # ./greetings/friends/cat.py class Cat: def __init__(self): self.phrase = "meow" ./greetings/friends ├── __init__.py ├── cat.py ├── dog.py └── human.py
  8. #pyconapac_5 スコープ 27 変数や関数、クラスなどの名前が参照可能な範囲 from greetings.friends.cat import Cat global_cat =

    Cat() def greet_cat_and_dog(): from greetings.friends.dog import Dog local_dog = Dog() local_cat = Cat() Global Local
  9. #pyconapac_5 Import の役割 29 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが

    存在する場所 から import を実行した名前空間へ追加する どこから 何を どこへ ? ✅ ✅
  10. #pyconapac_5 31 同名で Import 済みの モジュールオブジェクトはあるか? 再利用 モジュールを探索 ModuleSpec はあるか?

    ModuleSpec に書かれた Loader を起動 ModuleNotFoundError Import の仕組み - ModuleNotFoundError まで -
  11. #pyconapac_5 32 同名で Import 済みの モジュールオブジェクトはあるか? 再利用 モジュールを探索 ModuleSpec はあるか?

    ModuleSpec に書かれた Loader を起動 ModuleNotFoundError Import の仕組み - ModuleNotFoundError まで -
  12. #pyconapac_5 sys.modules 33 - インポート済み モジュールオブジェクトのリスト - どの名前空間にインポートしていても再利用可能 >>> import

    sys >>> sys.modules { 'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, … }
  13. #pyconapac_5 34 同名で Import 済みの モジュールオブジェクトはあるか? 再利用 モジュールを探索 ModuleSpec はあるか?

    ModuleSpec に書かれた Loader を起動 ModuleNotFoundError Import の仕組み - ModuleNotFoundError まで -
  14. #pyconapac_5 1. Pythonインタプリタに組み込まれているビルトインモジュールが対象。 sys.builtin_module_names で確認可 2. フリーズされているモジュールが対象(フリーズ: Python スクリプトをバイナリフォーマット に変換して、Python

    なしに直接実行できる形に変換すること) ModuleSpec の探索 sys.meta_path に設定されている Importer/Finder が探索。 主要なのは下記の3つ - BuiltinImporter1 - FlozenImporter2 - PathFinder → ModuleNotFoundError が起きやすい 36
  15. #pyconapac_5 PathFinder 37 下記パッケージやそれに含まれる .py/.pyc* ファイルを 対象として探索 - Python にプリインストールされているパッケージ

    - Python 環境にインストールしたパッケージ - 自作パッケージ * .py ファイルから生成されるバイトコードファイル 読み込みを高速化する目的で Import のタイミングで生成される
  16. #pyconapac_5 sys.path 38 PathFinder が探しに行くディレクトリのリスト 下記の数字順に探す 1. 実行したスクリプトがあるディレクトリ (指定されなかった場合はカレントディレクトリ) 2.

    PYTHONPATH 環境変数に設定されているディレクトリ 3. Python がデフォルトで検索するディレクトリ - Python のインストール方法によって異なる - site-packages ディレクトリとするのが慣例
  17. #pyconapac_5 PathFinder で ModuleSpec を探す 39 >>> from _frozen_importlib_external import

    PathFinder >>> spec = PathFinder.find_spec("greetings") # 見つからなかった場合は None が戻る >>> spec ModuleSpec( name='greetings', loader=<_frozen_importlib_external.SourceFileLoader object at 0x104eb0310>, origin='/foo/bar/greetings/__init__.py', submodule_search_locations=['/foo/bar/greetings'] )
  18. #pyconapac_5 40 同名で Import 済みの モジュールオブジェクトはあるか? 再利用 モジュールを探索 ModuleSpec はあるか?

    ModuleSpec に書かれた Loader を起動 ModuleNotFoundError Import の仕組み - ModuleNotFoundError まで -
  19. #pyconapac_5 よくある原因 43 分類 原因 モジュールがない 1. 異なる仮想環境を起動している 2. パッケージのインストールを忘れている

    モジュールはあるが 見つけられない 3. モジュール名を typo している 4. 同名のパッケージとモジュールがある 5. 自作パッケージが認識されていない 6. Python から見えないところにある
  20. #pyconapac_5 分類 原因 モジュールがない 1. 異なる仮想環境を起動している 2. パッケージのインストールを忘れている モジュールはあるが 見つけられない

    3. モジュール名を typo している 4. 同名のパッケージとモジュールがある 5. 自作パッケージが認識されていない 6. Python から見えないところにある よくある原因 44 実装を 見直す 環境を 見直す 起動方法を 見直す
  21. #pyconapac_5 原因1. 異なる仮想環境を起動している 45 対策: 起動している環境が期待どおりか確認する 環境を見直す # 仮想環境のディレクトリ >>>

    sys.prefix '/Users/tosh2230/Library/Caches/pypoetry/virtualenvs/unpacking -module-not-found-error-tiYZ59tI-py3.11' # 仮想環境の元となった Python のインストール先 >>> sys.base_prefix '/opt/homebrew/opt/[email protected]/Frameworks/Python.framework/ Versions/3.11'
  22. #pyconapac_5 原因2. パッケージのインストールを忘れている 46 対策: パッケージの一覧を確認して、なければインストールする 環境を見直す # インストールパッケージ一覧を表示 $

    pip list # カレントディレクトリから参照できるモジュールやパッケージの一覧 # モジュールを Import する前にも使える >>> help(‘modules’)
  23. #pyconapac_5 原因3. モジュール名を typo している 47 対策: 1. IDE に誤りを指摘してもらう

    2. モジュールの有無を確認する linter でチェックする - pylint - mypy など 環境を見直す 実装を見直す
  24. #pyconapac_5 原因4. 同名のパッケージとモジュールがある 48 判別方法: 1. 標準モジュールやインストールしたパッケージと 同じ名称のモジュールはないか? → 同名の場合は自作モジュールが優先される

    2. 自作パッケージと同じ名称のファイルはないか? 対策: モジュールの命名を再検討する → モジュールの役割を十分に考慮すると、名前は意外と被らない 実装を見直す
  25. #pyconapac_5 原因5. 自作パッケージが認識されていない 49 判別方法: Import 可能なパッケージの一覧を確認する 対策: Regular package

    がおすすめ。__init__.py を配置する 実装を見直す # カレントディレクトリから参照できるモジュール(パッケージ含む)の一覧 >>> help(‘modules’) # パッケージ名を指定すると、属するモジュールやサブパッケージを表示 >>> help(‘modules greetings.friends’)
  26. #pyconapac_5 原因6. Python から見えないところにある 50 判別方法: 期待したパスが sys.path にセットされているか確認する$ python

    foo/bar.py 対策: 1. PYTHONPATH 環境変数の使用を検討する 2. 設計・実装段階で起動方法を考慮に入れる → どのように起動するかで sys.path の先頭に入る値は変わる - スクリプトを起動 - モジュールをスクリプトとして起動 起動方法を 見直す >>> sys.path
  27. #pyconapac_5 起動方法1: スクリプト 51 引数で指定したものを main モジュール* として実行する 指定可能なものは下記の3つ -

    Python ファイル - __main__.py を含むディレクトリ - __main__.py を含む zip ファイル $ python foo/bar.py * __name__ に ‘__main__’ が設定されたスクリプト環境のこと。 Python 標準ライブラリ __main__ トップレベルのスクリプト環境 を参照 sys.path の先頭には 指定したファイルを含むディレクトリ がセットされる
  28. #pyconapac_5 52 指定されたモジュールを探して、その内容を main モジュールとして実行 指定されたのがパッケージなら、そのパッケージの __main__.py を実行 sys.path の先頭には

    カレントディレクトリ がセットされる # 指定するのはファイルではなく モジュール名 $ python -m foo.bar 起動方法2: モジュール* * PEP 338 – Executing modules as scripts
  29. #pyconapac_5 Python インタプリタで import を再現してみよう 53 # ModuleSpec を探す >>>

    from _frozen_importlib_external import PathFinder >>> src_spec = PathFinder.find_spec("src") >>> greetings_spec = PathFinder.find_spec("src.greetings", src_spec.submodule_search_locations, None) # モジュールをロード >>> from importlib.util import module_from_spec >>> greetings = module_from_spec(greetings_spec) >>> greetings_loader = greetings_spec.loader >>> greetings_loader.exec_module(greetings) # モジュール内の関数を実行 >>> greetings.greet_all()
  30. #pyconapac_5 56 同名で Import 済みの モジュールオブジェクトはあるか? 再利用 モジュールを探索 ModuleSpec はあるか?

    ModuleSpec に書かれた Loader を起動 ModuleNotFoundError Import の仕組み - ModuleNotFoundError まで -
  31. #pyconapac_5 分類 原因 モジュールがない 1. 異なる仮想環境を起動している 2. パッケージのインストールを忘れている モジュールはあるが 見つけられない

    3. モジュール名を typo している 4. 同名のパッケージとモジュールがある 5. 自作パッケージが認識されていない 6. Python から見えないところにある よくある原因 57 実装を 見直す 環境を 見直す 起動方法を 見直す
  32. #pyconapac_5 ドキュメント紹介 58 Python チュートリアル 6. モジュール モジュールとパッケージの解説 Python チュートリアル

    9.2. Python のスコープと名前空間 名前空間とスコープの解説 Python 標準ライブラリ importlib Approximating importlib.import_module() Import が主にやっていることを Python コードで示している Python 言語リファレンス 5. インポートシステム Import の詳細な仕様解説