絶対インポートや相対インポートに関して誤解していたことがあるので、インポートを再分類してまとめ直してみました。
なお、環境構築にPyCharmを使用しています。
更新履歴
2022/03/13 : 初版
環境
- WIndows10
- Python3.10
- PyCharm Community Edition 2021.2.2
Pythonの検索パス
インポートについて触れる前に、Pythonがどうやってインポート先のモジュールやパッケージを探しているのか?について書きたいと思います。
まず重要なこととして、この検索パスというのは環境変数のPathとは別物です。
検索パスのリストはsys.pathに格納されています。
試しに次のようなスクリプトを用意します。
import sys
for path in sys.path:
print(path)
C:\workにこのファイルを置き、コマンドプロンプト上で次のように実行します。
python "C:\work\search_path.py"
C:\work
C:\Users\username\AppData\Local\Programs\Python\Python310
C:\Users\username\AppData\Local\Programs\Python\Python310\lib
C:\Users\username\AppData\Local\Programs\Python\Python310\lib\site-packages
C:\Users\username\AppData\Local\Programs\Python\Python310\DLLs
C:\Users\username\AppData\Local\Programs\Python\Python310\Python310.zip
-
C:\work
search_path.pyを置いたフォルダ -
C:\Users\username\AppData\Local\Programs\Python\Python310
グローバルのpython.exeが存在するフォルダ -
C:\Users\username\AppData\Local\Programs\Python\Python310\lib
グローバルの標準モジュールが存在するフォルダ -
C:\Users\username\AppData\Local\Programs\Python\Python310\lib\site-packages
グローバルにpipでインストールしたパッケージが存在するフォルダ -
などなど
今度はPyCharmのTerminalから実行してみます。
myprojectというプロジェクトを作成し、Terminalを開きます。
仮想環境が起動していることを確認してください。
【参考】PyCharmのTerminalで仮想環境が自動起動せずに躓いた話
Python Interpreter生成時にInherit global site-packagesにチェックを入れていなかった場合、次のようになると思います。
C:\work
C:\Users\username\AppData\Local\Programs\Python\Python310
C:\Users\username\AppData\Local\Programs\Python\Python310\lib
C:\Users\username\AppData\Local\Programs\Python\Python310\DLLs
C:\Users\username\AppData\Local\Programs\Python\Python310\Python310.zip
C:\work\myproject\venv
C:\work\myproject\venv\lib\site-packages
グローバルのsite-packagesが消え、代わりに仮想環境のsite-packagesが追加されています。
Python Interpreter生成時にInherit global site-packagesにチェックを入れていた場合、次のようになると思います。
C:\work
C:\Users\username\AppData\Local\Programs\Python\Python310
C:\Users\username\AppData\Local\Programs\Python\Python310\lib
C:\Users\username\AppData\Local\Programs\Python\Python310\lib\site-packages
C:\Users\username\AppData\Local\Programs\Python\Python310\DLLs
C:\Users\username\AppData\Local\Programs\Python\Python310\Python310.zip
C:\work\myproject\venv
C:\work\myproject\venv\lib\site-packages
グローバルのsite-packagesと仮想環境のsite-packagesが両方存在しますね。
Pythonでは、これらのフォルダの中にインポート先が存在するかを検索している訳です。
ちなみにこの検索パスは自分で追加することもできます。
【参考】Python内のデフォルトパスを通す方法(Windows, Linux)- Qiita
Pythonのインポートの種類
Pythonには3種類のインポート方法があります。
- 絶対インポート
- 明示的な相対インポート
- 暗黙的な相対インポート
ですが、この3つで考えることが混乱のもとになると思うので、ここでは以下の4種類にわけて考えてみます。
1. 外部からのインポート
以下の3点を「外部からのインポート」の定義とします。
- 標準モジュールからのインポート
-
pip等でインストールしたパッケージからのインポート - 自分でPython検索パスに追加したパッケージからのインポート
一般的に言う絶対インポートの一部であり、こういうやつです。
import os
import sys
from tkinter import filedialog
import numpy
from openpyxl import load_workbook
これらは作業中のプロジェクトとは全く別のものなので、「外部」として切り出しました。
2. (内部)プロジェクト基準のインポート
スタートをプロジェクトのソースフォルダとするインポート方法を「プロジェクト基準のインポート」と呼ぶことにします。
一般的に言う絶対インポートの一部であり、こういうやつです。
myproject
└ myproject
├ __init__.py
├ main.py
├ sub.py
└ mypackage
├ __init__.py
├ module1.py
└ module2.py
from myproject import sub
from myproject.mypackage import module1
from myproject.mypackage import module2
from myproject.mypackage import module2
上の例ですとmyproject\myprojectがソースフォルダに相当します。
プロジェクト基準のインポートを使用するには、ソースフォルダが検索パス上に存在する必要があります。
この方法では、自分がソースフォルダ内のどの場所にいるか、ということは関係ありません。
実際、subとmypackage.module1でmypackage.module2をインポートする書き方が同じになってますね。
また、ソースフォルダも一種のパッケージですから、自分が何らかのパッケージに属していなければプロジェクト基準のインポートは使用できません。
3. (内部)パッケージ基準のインポート
スタートを自分が属するパッケージとするインポート方法を「パッケージ基準のインポート」と呼ぶことにします。
一般的に言う明示的な相対インポートであり、こういうやつです。
myproject
└ myproject
├ __init__.py
├ main.py
├ sub.py
└ mypackage
├ __init__.py
├ module1.py
└ module2.py
from . import sub
from .mypackage import module1
from .mypackage import module2
from . import module2
上の例ですとmain.py, sub.pyであればmyprojectが、module1.py, module2.pyであればmyproject.mypackageが自分が属するパッケージに相当します。
そして、自分が属するパッケージをimport文中では1点ピリオド.で表します。
当然のことですが、自分が何らかのパッケージに属していなければパッケージ基準のインポートは使用できません。
また、パッケージ構造の情報は呼び出し元から与える必要があります。
プロジェクト基準のインポートと比較してみましょう。
sub.pyから見てmodule2.pyは、プロジェクト基準であればmyproject.mypackageにあり、パッケージ基準でも(myproject).mypackageにあります。
一方module1.pyから見てmodule2.pyは、プロジェクト基準であればmyproject.mypackageにありましたが、パッケージ基準では両者は同じパッケージ(myproject.mypackage).に属していることになります。
4. (内部)ファイル基準のインポート
スタートを自分自身のファイルとするインポート方法を「ファイル基準のインポート」と呼ぶことにします。
一般的に言う暗黙的な相対インポートであり、こういうやつです。
myproject
└ myproject
├ __init__.py
├ main.py
├ sub.py
└ mypackage
├ __init__.py
├ module1.py
└ module2.py
import sub
from mypackage import module1
from mypackage import module2
import module2
インポート先が同フォルダにある場合、fromが省略されます。
なおこの方法については、Python3では使用が禁止されています。
補足
-
絶対インポート
= 外部からのインポート
+ プロジェクト基準のインポート
+ パッケージ外におけるファイル基準のインポート -
明示的な相対インポート
= パッケージ基準のインポート -
暗黙的な相対インポート
= パッケージ内におけるファイル基準のインポート -
ファイル基準のインポートがPython3で禁止されているというのは少し嘘で、正確には「パッケージ内におけるファイル基準のインポート(=暗黙的な相対インポート)」がPython3で禁止されています。
-
「パッケージ外におけるファイル基準のインポート(絶対インポートの一部)」であれば使用可能ではありますが、そもそも外部以外の関連モジュールが2つ以上になった時点でパッケージ化すべきだと思います。
(暗黙的な相対インポートを禁止した理由を考慮すれば。) -
なので、いっそのことファイル基準のインポート自体を(ルールとして)禁止して「1ファイルから成るスクリプト」か「パッケージ」しか構築しないようにしても良いのではと考えています。
実行ファイルのフォルダには検索パスが通ってしまうので、システムとして防ぐということができないですからね。 -
また、Pythonスクリプトを「ファイル名を指定して実行」する場合、そのファイルはどのパッケージにも属さないものとして扱われます。
そのため、「プロジェクト基準のインポート」「パッケージ基準のインポート」を使用しているファイルを「ファイル名を指定して実行」することはできません。
まとめ
どうすれば混乱を防げるか?を考えた結果、このような解釈に辿り着きました。
まずは「ざっくりとルールを把握(ファイル基準のインポート禁止)」し、慣れてきたら「注意が必要なこと(パッケージ外におけるファイル基準のインポート)」を理解する......というのが良いのかなと思います。