[Python]自作のモジュール・パッケージのimport

Python

いろいろモジュールを作ってくると、使いまわしたいと思うことがあると思います。

そのときに、パスが通ってなくてエラーが出てしまったり、パスが通っているはずなのになぜか呼び出せないことがあって、試行錯誤したすえ理解できたのでまとめました。

このページで扱うもの

  • 同じ階層にある自作モジュールのインポートと呼び出し方
  • __init__.pyの役割
  • サブパッケージのモジュールから上位パッケージのモジュールのインポート
  • 相対パスでインポート

別の階層にあるモジュールをインポートする方法は↓

自作パッケージをインストールすると、どこからでも呼べるようになり便利です。

参考ページ
モジュール――Python3 ドキュメント

使用した環境
Windows10
WSL2 - Ubuntu 20.04 LTS
Python 3.8.10

 Androidアプリを作成しました。
 感情用のメモ帳です。

スポンサーリンク
スポンサーリンク

同じ階層にあるモジュールの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関数を呼び出します。

ファイル構成のイメージ

ディレクトリ「greeting」と「main.py」が同じ階層。greetingのなかに「__init__.py」と「hello.py」。

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モジュールを読み込みます。

ファイル構成のイメージ

main.pyとディレクトリ「greeting」がある。greetinのなかに、ディレクトリ「farewell」、__init__.py`、hello.py。farewellのなかに__init__.pyとgoodbye.py。

※__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です。

自分で書いていてもややこしいですね。
図を用意したのでそちらを参考にしてください。

ファイル構成のイメージ

main.pyとディレクトリ「greeting」がある。greetinのなかに、ディレクトリ「farewell」、__init__.py`、hello.py。farewellのなかに__init__.pyとgoodbye.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はパッケージの目印であり、インポートしたときに一番最初に読み込まれるもの。

相対パスでインポートするとき、「.」はモジュール自身の所属しているディレクトリ、「..」はひとつ上のディレクトリを表す。

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