理解して覚えるpython ~import編~
はじめに
私は毎回「あれ,この import の仕方っていけるんだっけ」となっては調べて,を繰り返しています.そのような方,実は結構多いのではないのでしょうか.そんな自分とおさらばするための記事です.
明らかな嘘がありましたら,ご教授いただければ幸いです.
本記事では,python の import 方法である「絶対 import」と「相対 import」のルールを確認し,そのルールが決められた理由を理解して覚えることを目的としています.色々と調べた上,独自の観点でまとめています.
絶対 import
「ドット(.)」をつけない import 方法です.
この import が可能なファイルは,
import sys # これが絶対 import
print(sys.path)
で確認できます.私の環境では次のように出力されました.
['C:\\Users\\ユーザ名\\OneDrive\\デスクトップ\\実行ファイルがあるディレクトリ',
'C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310\\python310.zip',
'C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310\\DLLs',
'C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310\\lib',
'C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310',
'C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages']
この中に含まれるのは,PYTHONPATH とインストール時に指定したデフォルトパス('C:\\Users\\ユーザ名\\AppData\\Local\\Programs\\Python\\Python310'
)です.謎の文字列がいくつか見られますが,重要なものは以下の通りです.
- 実行ファイルが存在するディレクトリ
- 標準ライブラリ関連(python310.zip, lib)
- 外部ライブラリ(site-packages)
つまり,もともと入っている 「標準ライブラリ(random や math など)」,後から(pip などで)インストールした 「外部ライブラリ(numpy や matplotlib など)」 や 「実行ファイルがあるディレクトリ直下のファイル」 は何も考えずにそのまま import できるわけですね.
相対 import
「ドット(.)」を使った import 方法です.
注意点はいくつかありますが,まずその前に相対 import とは何かを確認しておきます.「ファイルを相対位置で import する方法でしょ?」と思った方は,「同じパッケージ内で他のモジュールを import する方法」 と覚えなおすことをお勧めします.というのも,前者の理解では,どのファイルからでも相対位置を指し示せば import できると勘違いしてしまうからです.
しかし,実際は次のようなルールがあります.
- 実行ファイルから相対 import できない[1]
- 実行ファイル以外(モジュール同士)は相対 import 可能だが,実行ファイル以上の階層の通るパッケージやモジュールは相対 import できない
これらのルールを頭の片隅において,前述した 「同じパッケージ内で他のモジュールを import する」 を理解していきましょう.
以下のディレクトリ構成を考えます.
mypackage:
├─main.py
├─subpack1
│ └─ mod1.py
└─subpack2
├─subsubpack
│ └─ mod3.py
└─ mod2.py
まず,1(実行ファイルから相対 import できない)についてですが,実行ファイルから相対的に import したいのであれば絶対 import で事足ります.なぜなら,実行ファイルは自然と sys.path に追加されるからです.具体的には,以下のように相対的に import 可能です.(補足ですが,相対 import はパッケージ間の import を簡単にするためにやむなく生み出された歴史的背景を知っていればこのことを意識する必要はありません.)
import subpack1.mod1 as mod1
import subpack2.mod2 as mod2
import subpack2.subsubpack.mod3 as mod3
# from .subpack1 import mod1 は不可
続いて,ルール2(実行ファイル以外(モジュール同士)は相対 import 可能だが,実行ファイル以上の階層の通るパッケージやモジュールは相対 import できない)です.先ほどのディレクトリ構成を再掲しておきます.
mypackage:
├─main.py
├─subpack1
│ └─ mod1.py
└─subpack2
├─subsubpack
│ └─ mod3.py
└─ mod2.py
この図でいうと,実行ファイル(main.py
)以外のファイルは,mod1.py
, mod2.py
, mod3.py
です.これらはモジュール同士であるため,互いに相対 import 可能です.
しかし,ここで注意すべき点が1つあります.それは,実行ファイル以上の階層を通った import ができないという点です.ここでの実行ファイルは,main.py
なので,これと同階層であるsubpack1, subpack2は互いに相対 import できません.
具体例でみてみます.
from ..subpack2 import mod2
print(mod2.a)
a = 3
これを先ほどのmain.py
から実行すると,
ImportError: attempted relative import beyond top-level package
「トップレベルパッケージ(ここではmain.py
)を越えた相対 import はできない」と怒られました.
一方で,subsubpack 内のmod3.py
からmod2.py
にアクセスすることは可能です.実行ファイルであるmain.py
を経由しないためです.
具体的なイメージができたところで2の理由を考えてみましょう.2は「モジュール間で相対 import 可能」と「実行ファイル以上を遡って相対 import は不可能」の2点でした.
まず,1つ目ですが,相対 import の利点を考えます.それは特定のファイルを起点に考えられる点です.絶対 import にすると,互いのモジュールを読み込む際に,「長いパスを書く」or/and「ディレクトリ構成を変更した場合,パスをほとんど全て書き直す」ことが必要になります.つまり,(パッケージ内の)モジュール間の読み込みができるだけに楽にやれるように許されている という認識でよさそうです.
続いて,2つ目の実行ファイル以上を遡れない理由は,セキュリティの観点での説明がなされており,個人的にもしっくりきます.例えば,外部から入手したパッケージがどこまでも遡ってファイルを操作できてしまったらどうでしょう.考えただけでやばそうです.つまり,信頼できないものの影響範囲を限定しているということですね.
PEP8 による規約
python のコーディング規約である PEP8 では以下のことが推奨されているため,意識すると良いと思います.特に重要だと思われる 3 つをピックアップしてお伝えします.
import はえらいやつ順!
「標準ライブラリ
複雑な構成でなければ絶対 import を使用する
可読性の観点ときれいなエラーを吐いてくれるかららしいですね.しかし,絶対 import では冗長になってしまうケースでは許容されるようです.
ワイルドカード(*)を使用するのは避ける
from os import *
のように書くと,何が読み込まれているかわかりづらいです.ワイルドカードでは変数名等に重複があった場合,知らないところで上書きされてバグの温床になり得ます.
おわりに
色々と調べた結果かなり時間を要してしまいましたが,私自身も非常に勉強になりました.ルールとともにその理由まで調べていくことで単なる暗記ではなく,ある程度納得感をもって覚えられ,言語を扱う上での汎用的な考え方も身につくのではないかと考えています.
今後も,このような思考で記事を書いていければと思っています.
参考
- Pythonでは何故、上位ディレクトリ内の別ディレクトリにあるファイルをimportできないのでしょうか? (sys.path.append() でできるのは解ったけど、仕組みがいまいちわからん)
- import 文ってなに?
- Python: 明示的な相対インポートの使い方
- Python: 標準ライブラリの格納場所
- Pythonのimportがよくわからなかったので、調べてみる
- Pythonの相対インポートで上位ディレクトリ・サブディレクトリを指定
- 【Python3基礎】同じモジュール内のファイルを呼ぶときはfrom <相対パス> import <ファイル名>じゃないとダメ
- PEP 328 - 複数行の import
そして 絶対 import と相対 import - pep8-ja
-
-mオプションをつければ可能 ↩︎
Discussion