いろいろモジュールを作ってくると、使いまわしたいと思うことがあると思います。
そのときに、パスが通ってなくてエラーが出てしまったり、パスが通っているはずなのになぜか呼び出せないことがあって、試行錯誤したすえ理解できたのでまとめました。
このページで扱うもの
- 同じ階層にある自作モジュールのインポートと呼び出し方
- __init__.pyの役割
- サブパッケージのモジュールから上位パッケージのモジュールのインポート
- 相対パスでインポート
別の階層にあるモジュールをインポートする方法は↓
自作パッケージをインストールすると、どこからでも呼べるようになり便利です。
使用した環境
Windows10
WSL2 - Ubuntu 20.04 LTS
Python 3.8.10
同じ階層にあるモジュールのimport
実行ファイルとモジュールが同じディレクトリにあるなら、標準ライブラリをインポートするときと同じです。
二つのファイルがあり、読まれる側のファイルが「hello.py」、実行ファイルが「main.py」とします。
コードは次のようになります。
hello.py
def output():
print('Hello!')
main.py
import hello
hello.output()
ターミナルから実行してみます。
$ python main.py Hello!
特に難しいところはないと思いますが、importやfrom~importについては以下にまとめています。
同じ階層のパッケージ内モジュールをimport
実行ファイルとパッケージが同じ階層だとします。
パッケージとするディレクトリに「__init__.py」という名前のファイルを追加します。
中身が空のファイルです。
Pythonのversion3.3から__init__.pyがなくてもパッケージを読み込むことができるようになっています。ただし、その場合は名前空間パッケージという通常のパッケージとは異なるものになります。通常は__init__.pyをパッケージに収めます。
実行する側を「main.py」、パッケージの名前を「greeting」とし、パッケージの中には「__init__.py」と「hello.py」があるとします。
main.pyからhello.pyのoutoput関数を呼び出します。
ファイル構成のイメージ
hello.py
def output():
print('Hello')
main.py
import greeting.hello
greeting.hello.output()
$ python main.py Hello!
mainのコード「import パッケージ名.モジュール名」でインポートし、呼び出すときは「パッケージ名.モジュール名.関数名」のように書きます。
一般的なパスの書き方に似ていますね。
下の階層を表すのにスラッシュ(/)ではなくドット(.)を使っていることと、ファイルの中身までたどれる点は異なりますが。
from~importを使った場合です。
main.py
from greeting import hello
hello.output()
from greeting.hello import outputのように書くこともできます。
ただそうすると、呼び出す際はoutput()だけになるので、コードの量が多くなってくるとどのモジュールから呼ばれたのかわからなくなり、可読性が損なわれる可能性があります。
__init__.pyの役割
__init__.pyの入っているディレクトリは、Pythonがパッケージだと認識します。
また__init__.pyはパッケージをインポートしたときに一番最初に読み込まれるモジュールです。
一番最初に読み込まれることを確認するために、__init__.pyにコードを追加します。
__init__.py
print('Hi! from init.py')
main.py
from greeting import hello
hello.output()
$ python main.py Hi! from init.py Hello!
main.pyではhelloモジュールのoutoput関数しか呼び出していません。
ですが__init__.pyで記述したprint文が実行されています。
__init__.pyのなかでhelloモジュールのoutput関数をインポートしてみます。
__init__.py
from greeting.hello import output
main.py
import greeting
greeting.output()
実行ファイルからパッケージをインポートすると、__init__.pyが読み込まれて、outputへのパスが通りました。
これでgreeting.output()――パッケージ名を使って関数が呼び出せるようになりました。
サブパッケージのモジュールをimport
greetingパッケージにサブパッケージ(farewell)を追加し、その中にモジュールgoodbye.pyと__init__.pyを追加しました。
goodbyeモジュールにはhello.pyと同じようなoutput関数を定義しています。
main.pyからgoodbyeモジュールを読み込みます。
ファイル構成のイメージ
※__init__.pyの中身は空です。
goodbye.py
def output():
print('Goodbye!')
main.py
from greeting.farewell import goodbye
goodbye.output()
$ python main.py Goodbye!
fromの後にパッケージとサブパッケージをドットでつなぎ、importの後でモジュール名を指定しています。
さらにパッケージを追加して階層が深くなったとしても、対象までドットでつないでたどり着けます。
また、上記のmain.pyは、
from greeting.farewell.goodbye import output
のように書くこともできますが、この場合も、hello.pyにoutputが定義されているので、両方を呼んだときにどちらの関数なのか戸惑うかもしれません。
サブパッケージのモジュールから上位にあるモジュールをimport
ファイル構成はそのままで、モジュールの関数を文字列を返すものに変更します。
サブパッケージ内のgoodbyeモジュールからその上位のパッケージのhelloモジュールを読み込んでみます。
実行ファイルはmain.pyです。
自分で書いていてもややこしいですね。
図を用意したのでそちらを参考にしてください。
ファイル構成のイメージ
hello.py
def output():
return 'Hello'
goodbye.py
from greeting import hello
def output():
return 'Goodbye!'
def hello_from_goobye():
return hello.output()
main.py
from greeting.farewell import goodbye
print(goodbye.output())
print(goodbye.hello_from_goobye())
$ python main.py Goodbye! Hello!
goodbyeモジュールにおいて、上位のパッケージをインポートするときには、最上位のパッケージ(この場合はgreeting)から記述します。
そのモジュールで定義したhello_from_goobyeは、実行ファイルが最上位にあるパッケージと同じ階層であることを前提とした記述です。
パッケージやモジュールを探すときに、実行ファイルのカレントディレクトリ内をたどっていくことはできますが、カレントディレクトリの外側や上へ探しに行くことができません。
ですので、もしgoodbyeモジュールを実行ファイルとすると、探しにいけるのはfarewellのなかだけとなり、コンソールでは、
Traceback (most recent call last): File "/home/user/code/greeting/farewell/goodbye.py", line 1, in from greeting import hello ModuleNotFoundError: No module named 'greeting'
「greeting」という名前のモジュール(パッケージ)がありません、とエラーが出ます。
相対パス
from~import文では、ドット(.)でパスが始まっているときには相対パスとして扱われます。
ドットはモジュールから見て、自身が所属しているディレクトリを表します。
Unix系のパスの表し方と同じですね。
greetingパッケージにneohelloモジュールを追加します。neohelloとhelloモジュールは同じ階層にあります。
neohello.py
from greeting.hello import output
def neo():
return output() + '!!!!!'
上の一行目を相対パスで書くと↓
from .hello import output
のようになります。
neohelloのカレントディレクトリはgreetingなのでその部分がドットに置き換わっています。
さらに「..」のようにドットを2つ続けて書くと、カレントディレクトリのひとつ上のディレクリを指します。
今度はgoodbyeモジュールに視点を移すと、カレントディレクトリがfarewellで、その上がgreetingになります。
goodbye.pyの一行目、
from greeting import hello
を相対パスにすると、
from .. import hello
のように書けます。
ただし相対パスで書くと可読性が悪くなるので注意が必要です。
メリットとして、パッケージ名を変更したときにimport文も合わせて直す必要がなくなります。
まとめ
自作モジュールのインポートについてまとめてきました。
読み込みも、呼び出しも「パッケージ名.モジュール名」のように、階層ごとにドットでつなげて記述する。
__init__.pyはパッケージの目印であり、インポートしたときに一番最初に読み込まれるもの。
相対パスでインポートするとき、「.」はモジュール自身の所属しているディレクトリ、「..」はひとつ上のディレクトリを表す。