絶対インポートや相対インポートに関して誤解していたことがあるので、インポートを再分類してまとめ直してみました。
なお、環境構築に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スクリプトを「ファイル名を指定して実行」する場合、そのファイルはどのパッケージにも属さないものとして扱われます。
そのため、「プロジェクト基準のインポート」「パッケージ基準のインポート」を使用しているファイルを「ファイル名を指定して実行」することはできません。
まとめ
どうすれば混乱を防げるか?を考えた結果、このような解釈に辿り着きました。
まずは「ざっくりとルールを把握(ファイル基準のインポート禁止)」し、慣れてきたら「注意が必要なこと(パッケージ外におけるファイル基準のインポート)」を理解する......というのが良いのかなと思います。