Slide 1

Slide 1 text

#pyconapac_5 ModuleNotFoundErrorの 傾向と対策 PyCon APAC 2023 2023-10-27(Day1) 仕組みから学ぶ Import

Slide 2

Slide 2 text

#pyconapac_5 堤 利史 Toshifumi Tsutsumi @tosh2230 Data Engineer GMO Pepabo, inc. 風来のシレン 最新作を待ちわびています 2 自己紹介

Slide 3

Slide 3 text

#pyconapac_5 3 PyCon JP 2021 Loggingモジュールではじめるログ出力入門 / Introduction to Python Logging

Slide 4

Slide 4 text

#pyconapac_5 4 PyCon JP 2022 SQLクエリ解析によるE2Eデータリネージの実現 / E2E-data-lineage

Slide 5

Slide 5 text

#pyconapac_5 このスライドは公開されています 5

Slide 6

Slide 6 text

#pyconapac_5 目的 🎯 6 ModuleNotFoundError の 解消にかける時間を減らすこと

Slide 7

Slide 7 text

#pyconapac_5 話すこと 😀 7 - ModuleNotFoundError - Import の仕組み(特に探す部分)

Slide 8

Slide 8 text

#pyconapac_5 話さないこと 😷 8 - Python 環境構築 - パッケージ管理ツール

Slide 9

Slide 9 text

#pyconapac_5 表記例 🖋 9 # これはコメント # 先頭が $ はコマンド $ bash # 先頭が >>> は Python インタプリタ >>> import os # 先頭に何もなければ Python モジュールのコード import os

Slide 10

Slide 10 text

#pyconapac_5 1. Pythonのモジュールと ModuleNotFoundError 2. Importの役割 3. Importの仕組み 4. ModuleNotFoundErrorの 傾向と対策 10 Contents 📋

Slide 11

Slide 11 text

#pyconapac_5 1. Pythonのモジュールと ModuleNotFoundError 2. Importの役割 3. Importの仕組み 4. ModuleNotFoundErrorの 傾向と対策 11 Contents 📋

Slide 12

Slide 12 text

#pyconapac_5 12 ModuleNotFoundError: No module named 'foo'

Slide 13

Slide 13 text

#pyconapac_5 13 ModuleNotFoundError: No module named 'foo'

Slide 14

Slide 14 text

#pyconapac_5 14 そもそも モジュール とは?

Slide 15

Slide 15 text

#pyconapac_5 モジュールとは 15 - 定義や文をファイルに保存、再利用できるようにしたもの - 拡張子が .py のファイル - Python コードの組織単位として機能する - モジュールに含まれるものの例 - 関数 - クラス - 変数

Slide 16

Slide 16 text

#pyconapac_5 - 複数のモジュールやサブパッケージを “ドット付きモジュール名”で構造化したもの - foo.bar は、 foo パッケージの モジュール bar - 多くのパッケージが PyPI; Python Package Index で公開されている - パッケージには2種類ある。今回は前者を紹介 - Regular packages - Namespace packages パッケージとは 16

Slide 17

Slide 17 text

#pyconapac_5 Regular packages 17 - __init__.py を含む単一のディレクトリ - 読み込まれるときに __init__.py が実行される - パッケージの初期化 - ディレクトリとパッケージが対応しているので わかりやすい

Slide 18

Slide 18 text

#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

Slide 19

Slide 19 text

#pyconapac_5 Import* 別のモジュールにある Python コードを 使えるようにすること モジュールは基本的に import して使う 19 * Import 文の解説は Python 言語リファレンス 7.11. The import statement を参照

Slide 20

Slide 20 text

#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

Slide 21

Slide 21 text

#pyconapac_5 話は戻って ModuleNotFoundError 21 「import を試みます」 「モジュールを探しました」 「見つかりませんでした」 greetings friends cat dog human ./greetings/friends ├── __init__.py ├── cat.py ├── dog.py └── human.py

Slide 22

Slide 22 text

#pyconapac_5 22 Import の 役割と仕組みが分かれば 原因を特定できるかも?

Slide 23

Slide 23 text

#pyconapac_5 1. Pythonのモジュールと ModuleNotFoundError 2. Importの役割 3. Importの仕組み 4. ModuleNotFoundErrorの 傾向と対策 23 Contents 📋

Slide 24

Slide 24 text

#pyconapac_5 Import の役割 24 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが存在する場所から import を実行した名前空間へ追加する どこから 何を どこへ ✅

Slide 25

Slide 25 text

#pyconapac_5 Import の役割 25 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが存在する場所から import を実行した 名前空間 へ追加する どこから 何を どこへ ✅ ?

Slide 26

Slide 26 text

#pyconapac_5 名前空間 26 - 変数や関数、クラス、モジュールの名前から オブジェクトへの対応関係を管理するもの - 各モジュールは専用の名前空間をもっているので 異なるモジュールに同じ名前の何かがあっても平気 >>> from greetings.friends import cat >>> cat

Slide 27

Slide 27 text

#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

Slide 28

Slide 28 text

#pyconapac_5 Import の役割 28 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが存在する場所から import を実行した名前空間へ追加する どこから 何を どこへ ✅ ✅

Slide 29

Slide 29 text

#pyconapac_5 Import の役割 29 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが 存在する場所 から import を実行した名前空間へ追加する どこから 何を どこへ ? ✅ ✅

Slide 30

Slide 30 text

#pyconapac_5 1. Pythonのモジュールと ModuleNotFoundError 2. Importの役割 3. Importの仕組み 4. ModuleNotFoundErrorの 傾向と対策 30 Contents 📋

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

#pyconapac_5 sys.modules 33 - インポート済み モジュールオブジェクトのリスト - どの名前空間にインポートしていても再利用可能 >>> import sys >>> sys.modules { 'sys': , 'builtins': , '_frozen_importlib': , … }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

#pyconapac_5 ModuleSpec* 35 モジュールの仕様に関する情報を扱うクラス - モジュールがある場所 - ロード方法の指定(loader) - 親モジュール・子モジュールの有無 など * PEP 451 – A ModuleSpec Type for the Import System

Slide 36

Slide 36 text

#pyconapac_5 1. Pythonインタプリタに組み込まれているビルトインモジュールが対象。 sys.builtin_module_names で確認可 2. フリーズされているモジュールが対象(フリーズ: Python スクリプトをバイナリフォーマット に変換して、Python なしに直接実行できる形に変換すること) ModuleSpec の探索 sys.meta_path に設定されている Importer/Finder が探索。 主要なのは下記の3つ - BuiltinImporter1 - FlozenImporter2 - PathFinder → ModuleNotFoundError が起きやすい 36

Slide 37

Slide 37 text

#pyconapac_5 PathFinder 37 下記パッケージやそれに含まれる .py/.pyc* ファイルを 対象として探索 - Python にプリインストールされているパッケージ - Python 環境にインストールしたパッケージ - 自作パッケージ * .py ファイルから生成されるバイトコードファイル 読み込みを高速化する目的で Import のタイミングで生成される

Slide 38

Slide 38 text

#pyconapac_5 sys.path 38 PathFinder が探しに行くディレクトリのリスト 下記の数字順に探す 1. 実行したスクリプトがあるディレクトリ (指定されなかった場合はカレントディレクトリ) 2. PYTHONPATH 環境変数に設定されているディレクトリ 3. Python がデフォルトで検索するディレクトリ - Python のインストール方法によって異なる - site-packages ディレクトリとするのが慣例

Slide 39

Slide 39 text

#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'] )

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

#pyconapac_5 Import の役割 41 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが存在する場所から import を実行した名前空間へ追加する どこから 何を どこへ ✅ ✅ ✅

Slide 42

Slide 42 text

#pyconapac_5 1. Pythonのモジュールと ModuleNotFoundError 2. Importの役割 3. Importの仕組み 4. ModuleNotFoundErrorの 傾向と対策 42 Contents 📋

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

#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'

Slide 46

Slide 46 text

#pyconapac_5 原因2. パッケージのインストールを忘れている 46 対策: パッケージの一覧を確認して、なければインストールする 環境を見直す # インストールパッケージ一覧を表示 $ pip list # カレントディレクトリから参照できるモジュールやパッケージの一覧 # モジュールを Import する前にも使える >>> help(‘modules’)

Slide 47

Slide 47 text

#pyconapac_5 原因3. モジュール名を typo している 47 対策: 1. IDE に誤りを指摘してもらう 2. モジュールの有無を確認する linter でチェックする - pylint - mypy など 環境を見直す 実装を見直す

Slide 48

Slide 48 text

#pyconapac_5 原因4. 同名のパッケージとモジュールがある 48 判別方法: 1. 標準モジュールやインストールしたパッケージと 同じ名称のモジュールはないか? → 同名の場合は自作モジュールが優先される 2. 自作パッケージと同じ名称のファイルはないか? 対策: モジュールの命名を再検討する → モジュールの役割を十分に考慮すると、名前は意外と被らない 実装を見直す

Slide 49

Slide 49 text

#pyconapac_5 原因5. 自作パッケージが認識されていない 49 判別方法: Import 可能なパッケージの一覧を確認する 対策: Regular package がおすすめ。__init__.py を配置する 実装を見直す # カレントディレクトリから参照できるモジュール(パッケージ含む)の一覧 >>> help(‘modules’) # パッケージ名を指定すると、属するモジュールやサブパッケージを表示 >>> help(‘modules greetings.friends’)

Slide 50

Slide 50 text

#pyconapac_5 原因6. Python から見えないところにある 50 判別方法: 期待したパスが sys.path にセットされているか確認する$ python foo/bar.py 対策: 1. PYTHONPATH 環境変数の使用を検討する 2. 設計・実装段階で起動方法を考慮に入れる → どのように起動するかで sys.path の先頭に入る値は変わる - スクリプトを起動 - モジュールをスクリプトとして起動 起動方法を 見直す >>> sys.path

Slide 51

Slide 51 text

#pyconapac_5 起動方法1: スクリプト 51 引数で指定したものを main モジュール* として実行する 指定可能なものは下記の3つ - Python ファイル - __main__.py を含むディレクトリ - __main__.py を含む zip ファイル $ python foo/bar.py * __name__ に ‘__main__’ が設定されたスクリプト環境のこと。 Python 標準ライブラリ __main__ トップレベルのスクリプト環境 を参照 sys.path の先頭には 指定したファイルを含むディレクトリ がセットされる

Slide 52

Slide 52 text

#pyconapac_5 52 指定されたモジュールを探して、その内容を main モジュールとして実行 指定されたのがパッケージなら、そのパッケージの __main__.py を実行 sys.path の先頭には カレントディレクトリ がセットされる # 指定するのはファイルではなく モジュール名 $ python -m foo.bar 起動方法2: モジュール* * PEP 338 – Executing modules as scripts

Slide 53

Slide 53 text

#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()

Slide 54

Slide 54 text

#pyconapac_5 54 まとめ

Slide 55

Slide 55 text

#pyconapac_5 Import の役割 55 > 別のモジュールにある Python コードを使えるようにすること 指定したモジュールを モジュールが存在する場所から import を実行した名前空間へ追加する どこから 何を どこへ ✅ ✅ ✅

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

#pyconapac_5 ドキュメント紹介 58 Python チュートリアル 6. モジュール モジュールとパッケージの解説 Python チュートリアル 9.2. Python のスコープと名前空間 名前空間とスコープの解説 Python 標準ライブラリ importlib Approximating importlib.import_module() Import が主にやっていることを Python コードで示している Python 言語リファレンス 5. インポートシステム Import の詳細な仕様解説

Slide 59

Slide 59 text

#pyconapac_5 59 Thank you ! https://github.com/tosh2230/pycon-apac-2023