Pythonのimportって難しくないですか??

備忘録

最近自分でモジュールを分けて開発することが多くなってきたのですが、import関連でつまづくことがしばしばあったので、まとめておきたいと思います。

と、書いて思いましたが、他の言語でも規模が大きめのコードを書いたことがあまりないので、別にPythonが特別難しいってわけではないかもです。私が弱いだけかもしれません。あ、目から水が

importの基本

import <モジュール名>

でimoprtできます。numpyをimportしたかったらimport numpyです。

import <モジュール名> as <別名>

で別名をつけることができます。import numpy as npとか、import pandas as pdとかをよく見ますね。

import <パッケージ名>.<サブパッケージ、またはモジュール名>

で、サブパッケージやモジュールを読み込むこともできます。

他のフォルダを見ようとするとややこしくなる

さて、ここまでは、名前を指定すれば良いだけなので、何も難しくありません。importするモジュールが

  • pip等でインストールしたモジュール
  • 同フォルダのモジュール

なら良いのですが、自分で作った別フォルダのモジュールをimportしようとすると途端に難しくなります。(とまと基準)

ちなみに、これまで「モジュールをimportする」と言っていますが、モジュールというのは、Pythonの定義や文が入った、拡張子がpyのファイルのことです。

以下のフォルダ構成を例に考えてみます。a1.pyやsub_a1.pyなどがモジュールです。

$ pwd
/tmp/a
$ tree
.
├── __init__.py
├── a1.py
├── a2.py
├── sub_a1
│   ├── __init__.py
│   └── sub_a1.py
└── sub_a2
    ├── __init__.py
    └── sub_a2.py

子フォルダのモジュールをimport

a1.pyからsub_a1/sub_a1.pyをimportするときは、

import sub_a1.sub_a1

でいけます。そんなに難しくはないですね。

親フォルダのモジュールをimport

sub_a1/sub_a1.pyからa1.pyをimportするときは、相対インポートの「..」を使って

from .. import a1

だけではimportできません。

なぜだ…と唸っていたのですが、どうやら親フォルダ「/tmp/a」にもパスが通っていないといけないようです。モジュールをimportするときのパスはsys.pathで確認できます。確認してみたら、確かにa1.pyが見える状態ではありませんでした。つまり、/tmp/aがなかったのです。

ちなみに、カレントディレクトリはsys.pathに入るようで、/tmp/a/sub_a1はありました。

一応解決策はあります。ないなら追加しましょう。sys.pathに親フォルダを追加すれば良いのです。

import sys
sys.path.append("/tmp/a")  # 「__file__/..」のようにしても可
import a1  # /tmp/a/a1.pyを読み込む

それか、コードはそのままで、python3 -mでモジュールとして指定すればimportできます。ただし、カレントディレクトリは読み込みたいモジュールの上の階層でないといけません。(この例ではa1.pyのフォルダの1個上の階層)

$ pwd                                                                                                                                                                                   /tmp
$ python3 -m a.sub_a1.sub_a1

実行場所が限定されるのは、良くない気がしますね。

子フォルダから別の子フォルダのモジュールをimport

a1.pyからsub_a1.pyをimportし、そのsub_a1.pyからsub_a2.pyをimportしたいときです。ん、、ややこしいですね。treeで見直すと、以下のような感じです。

$ pwd
/tmp/a
$ tree
.
├── __init__.py
├── a1.py  # ①ここから
├── a2.py
├── sub_a1
│   ├── __init__.py
│   └── sub_a1.py  # ②これをimportし、ここからさらに
└── sub_a2
    ├── __init__.py
    └── sub_a2.py  # ③これをimport

a1.pyから2つimportするのではありません。sub_a2.pyをimportするのはsub_a1.pyです。

さっきの例からして、③は難しそうですね。「またsys.pathをいじらないといけないのかな」と思っていたら、そうでもないようです。

a1.pyには

import sub_a1.sub_a1

と書き、sub_a1.pyには

import sub_a2.sub_a2

と書き、a1.pyを実行するとimportできてしまいます。a1.py -> sub_a1.py -> sub_a2.py、オールOKです。sub_a1.pyのimportコードを見たとき、sub_a1フォルダから(親フォルダをすっとばして)sub_a2を見ているので、違和感を感じますが、できるんです。理由は簡単で、a1.pyを実行したときのsys.pathがsub_a1.pyを読み込んだ時にも引き継がれているためです。つまり、import元(a1.py)の実行時点のsys.pathからimport先(sub_a2.sub_a2)が見えているので、importできるのです。

ただ、個人的には、やり方はおすすめできません。(こういうの、一般的にはどうなんでしょうか。アリなんでしょうか。)

なぜなら、sub_a1を単体で実行しようとすると

$ python3 sub_a1/sub_a1.py
Traceback (most recent call last):
  File "sub_a1/sub_a1.py", line 1, in <module>                                                                                                                                                                       import sub_a2.sub_a2                                                                                                                                                                                         ModuleNotFoundError: No module named 'sub_a2'

と、悲しい感じになるためです。「特定のファイル(ここではa1.py)からだけ読み込むならいいのでは?」と思うかもしれませんが、unittestモジュールなどでテストをしようとした際に苦労します。(苦労しました)

ただ、あくまで個人で書くような、「動けばよい」的なものであればそんなに気にしなくて良いと思いますけどね。

結論

親フォルダのモジュールはimportしようとしてはいけない。子フォルダのみimportすべき。

反論は認めます。

タイトルとURLをコピーしました