5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Pythonのインポートを誤解して躓いた話

Posted at

絶対インポートや相対インポートに関して誤解していたことがあるので、インポートを再分類してまとめ直してみました。
なお、環境構築にPyCharmを使用しています。

更新履歴

2022/03/13 : 初版

環境

  • WIndows10
  • Python3.10
  • PyCharm Community Edition 2021.2.2

Pythonの検索パス

インポートについて触れる前に、Pythonがどうやってインポート先のモジュールやパッケージを探しているのか?について書きたいと思います。
まず重要なこととして、この検索パスというのは環境変数のPathとは別物です。

検索パスのリストはsys.pathに格納されています。
試しに次のようなスクリプトを用意します。

search_path.py
import sys
for path in sys.path:
    print(path)

C:\workにこのファイルを置き、コマンドプロンプト上で次のように実行します。

pythonスクリプト実行
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
main.py
from myproject import sub
from myproject.mypackage import module1
sub.py
from myproject.mypackage import module2
mypackage\module1.py
from myproject.mypackage import module2

上の例ですとmyproject\myprojectがソースフォルダに相当します。
プロジェクト基準のインポートを使用するには、ソースフォルダが検索パス上に存在する必要があります。
この方法では、自分がソースフォルダ内のどの場所にいるか、ということは関係ありません。
実際、submypackage.module1mypackage.module2をインポートする書き方が同じになってますね。
また、ソースフォルダも一種のパッケージですから、自分が何らかのパッケージに属していなければプロジェクト基準のインポートは使用できません。

3. (内部)パッケージ基準のインポート

スタートを自分が属するパッケージとするインポート方法を「パッケージ基準のインポート」と呼ぶことにします。
一般的に言う明示的な相対インポートであり、こういうやつです。

プロジェクト構成
myproject
    └ myproject
        ├ __init__.py
        ├ main.py
        ├ sub.py
        └ mypackage
            ├ __init__.py
            ├ module1.py
            └ module2.py
main.py
from . import sub
from .mypackage import module1
sub.py
from .mypackage import module2
mypackage\module1.py
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
main.py
import sub
from mypackage import module1
sub.py
from mypackage import module2
mypackage\module1.py
import module2

インポート先が同フォルダにある場合、fromが省略されます。

なおこの方法については、Python3では使用が禁止されています。

補足

  • 絶対インポート
    = 外部からのインポート
    + プロジェクト基準のインポート
    + パッケージ外におけるファイル基準のインポート

  • 明示的な相対インポート
    = パッケージ基準のインポート

  • 暗黙的な相対インポート
    = パッケージ内におけるファイル基準のインポート

  • ファイル基準のインポートがPython3で禁止されているというのは少し嘘で、正確には「パッケージ内におけるファイル基準のインポート(=暗黙的な相対インポート)」がPython3で禁止されています。

  • パッケージ外におけるファイル基準のインポート(絶対インポートの一部)」であれば使用可能ではありますが、そもそも外部以外の関連モジュールが2つ以上になった時点でパッケージ化すべきだと思います。
    (暗黙的な相対インポートを禁止した理由を考慮すれば。)

  • なので、いっそのことファイル基準のインポート自体を(ルールとして)禁止して「1ファイルから成るスクリプト」か「パッケージ」しか構築しないようにしても良いのではと考えています。
    実行ファイルのフォルダには検索パスが通ってしまうので、システムとして防ぐということができないですからね。

  • また、Pythonスクリプトを「ファイル名を指定して実行」する場合、そのファイルはどのパッケージにも属さないものとして扱われます。
    そのため、「プロジェクト基準のインポート」「パッケージ基準のインポート」を使用しているファイルを「ファイル名を指定して実行」することはできません。

まとめ

どうすれば混乱を防げるか?を考えた結果、このような解釈に辿り着きました。
まずは「ざっくりとルールを把握(ファイル基準のインポート禁止)」し、慣れてきたら「注意が必要なこと(パッケージ外におけるファイル基準のインポート)」を理解する......というのが良いのかなと思います。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?