Q1パッケージとして認識されるために、フォルダの中に必ず置くべきファイルはどれですか?
__init__.py と相対インポート — パッケージで複数ファイルを束ねる
__init__.pyでパッケージを束ね公開APIを絞る書き方、from ... importとimportの違い、絶対インポートと相対インポートの棲み分けを実例で学べます。
アプリケーションが大きくなると、関連するモジュールを 1 つのフォルダにまとめてパッケージとして扱いたくなります。本記事では、パッケージの作り方と__init__.pyの役割、from .module import ...のような相対インポートを整理します。
パッケージとは __init__.py 入りのフォルダ
パッケージとは、__init__.pyという特別なファイルを含んだフォルダのことです。Python はこのファイルを見つけると、そのフォルダ全体を 1 つのパッケージとして扱い、外部からimport フォルダ名で読み込めるようにします。__init__.pyはパッケージが import されたときに最初に実行される場所で、ここで公開する関数やクラスを定義することが多いです。
- __init__.py — パッケージが import されたときに最初に実行されるファイル
- calculation.py — モジュール(add / multiply)
- string_utils.py — モジュール(format_name など)
my_package/フォルダに__init__.pyが入っているのでパッケージとして認識される。中のcalculation.pyなどのモジュールは__init__.pyが橋渡しして外に公開する。__init__.pyは空ファイルでも構いません。空でも、フォルダにそのファイルがあるだけで Python はパッケージとして認識します。
__init__.py を介さず直接 import する
__init__.pyが空ファイルでも、パッケージ内のモジュールは直接 import すれば呼び出せます。from パッケージ名.モジュール名 import 関数名のように、ファイル位置をドットでつなぐ書き方です。
# my_package/__init__.py ← 中身は空でも OK
# my_package/calculation.py
def add(a, b):
return a + b
# main.py から呼ぶ
from my_package.calculation import add # ← ファイル名 calculation まで明示する
print(add(1, 2)) # 3
このパターンでは、利用者がどのファイルに何があるかを知っている必要があります。後でファイル分割を変えると(例: calculation.py を 2 つに分けるなど)、利用側のコードも書き直さないといけません。次のセクションで紹介する__init__.pyでの再エクスポートは、これを「パッケージ名直下から短い名前で呼べる」形に整える仕組みです。
__init__.py で公開する関数を集める
__init__.pyの中でfrom .モジュール名 import 関数名と書いておくと、外部からはパッケージ名直下にその関数があるかのように見えるようになります。たとえば__init__.pyでfrom .calculation import add, multiplyと書けば、外側のファイルからはfrom my_package import add, multiplyと短く書けます。
__init__.pyで引き上げて公開すると、main.py からはファイル名 calculation を意識せずにfrom my_package import addと短く書ける。公開 API を __init__.py に集める意義
利用者が触るのは __init__.py が公開した関数・クラスだけ、と決めておくと、内部のファイル分割を後から自由に変えても利用側のコードを書き換えずに済みます。これは外側に対してカプセル化を効かせる、パッケージ設計の基本パターンです。
from と import — 2 つの書き方
import文には2 つの書き方があります — from パッケージ import 名前とimport パッケージです。どちらも対象を読み込む点は同じですが、呼び出すときの書き方が変わります。
from パッケージ import 名前は名前そのものをスコープに取り込むのでそのまま呼べる。import パッケージはパッケージ名のみが入り、関数呼び出しは完全パスで書く必要がある。# パターン 1: from ... import ...
from my_package.calculation import add
print(add(1, 2)) # add がそのまま使える
# パターン 2: import ...
import my_package.calculation
print(my_package.calculation.add(1, 2)) # 完全パスで呼ぶ必要がある
# パターン 2 + as エイリアス(長い名前を短くしたい)
import my_package.calculation as calc
print(calc.add(1, 2))
| 書き方 | スコープに入る名前 | 呼び出し方 |
|---|---|---|
| from my_package.calculation import add | add | add(1, 2) |
| import my_package.calculation | my_package | my_package.calculation.add(1, 2) |
| import my_package.calculation as calc | calc | calc.add(1, 2) |
よく使うのはfrom パッケージ import 名前の形で、関数名を短く呼べるため main 側のコードが読みやすくなります。ただし、異なるパッケージが同じ名前の関数を持っているときはimport パッケージの形でモジュール名を残しておくと、どちらの関数を呼んでいるかが一目で分かります。
- add — 名前そのものが main 側に入る
- 呼び方:
add(1, 2)でそのまま使える - 短く書ける反面、
addがどのパッケージ由来か一目で分からない
- my_pkg — パッケージ名だけが main 側に入る
- 呼び方:
my_pkg.calc.add(1, 2)と完全パスで書く - 長くなる代わりに、
my_pkg.calc.addで由来が明示される
from パッケージ import 名前は名前そのものを main 側のスコープに入れる (= 短く呼べる)。import パッケージはパッケージ名のみが入り、関数を呼ぶときは完全パスでドットをつなぐ (= どこから来たか追いやすい)。絶対インポートと相対インポート
パッケージ内のモジュール同士でお互いを参照したい、というケースを考えます。たとえばutility/validator.pyからutility/helper.pyを読み込みたいときの書き方は2 通りあります — 絶対インポートと相対インポートです。違いを理解するには、まずプロジェクトのルートが何を指すかを押さえる必要があります。
- main.py ← 最初に実行されるファイル
- config.py ← アプリ全体の設定値
- __init__.py ←
import my_appで最初に実行されるファイル
- __init__.py
- validator.py
- helper.py
main.pyが置いてあるフォルダ。絶対インポートfrom my_app.utility import validatorのmy_appは、このルート直下にある同名フォルダを指す。プロジェクトのルートとは、python main.pyのように最初に実行する main.py が置いてあるフォルダのことです。絶対インポートfrom my_app.utility import validatorを書くと、Python はこのルート直下からmy_appフォルダを探し、その中のutility/validator.pyをたどります。フォルダ構成をドットでつないだものが、絶対インポートのパスになります。
| 種類 | 書き方 | 意味 |
|---|---|---|
| 絶対インポート | from my_app.utility import helper | プロジェクトのルートからの完全パスで指定 |
| 相対インポート | from . import helper | 今いるファイルからの相対位置で指定 |
| 相対インポート(親) | from .. import config | 1 階層上のフォルダにあるファイルを指定 |
from . importの.は「自分と同じフォルダ」を表す。実行ファイル(main.py)から外部のパッケージを読み込むときは絶対インポートを使い、パッケージ内部のモジュール同士の参照には相対インポートを使う、という棲み分けが一般的です。相対インポートだと、後でパッケージ名(フォルダ名)を変えたときにも内部のコードを書き換えずに済みます。
# my_app/utility/validator.py
# 同じパッケージ内の helper.py を相対インポート
from .helper import log_message
def validate_user(user):
if user.name and user.email:
log_message("検証 OK")
return True
log_message("問題が発生しました")
return False
# my_app/utility/helper.py
def log_message(message):
print("[LOG]", message)
# 別パッケージにある config.py を読みたいときは、
# 1 つ上に上がる .. を使う:
# from ..config import get_config
- config.py ←
from ..config import ...の対象
- validator.py ←
from .helper import log_messageを書く側 - helper.py ←
log_message(...)を提供する側
from .helper import log_messageの.は「このフォルダ (= utility/)」を指す。config.pyは1 階層上の my_app/にあるので、validator から見ると..config (.. = 1 階層上) で参照できる。実行ファイルから相対インポートはできない
python main.pyのように直接実行されるファイルからは相対インポートを書けません(ImportError になります)。相対インポートは「自分がどこかのパッケージの一員として読み込まれている」前提で動く仕組みのためです。実行ファイル側からは、必ず絶対インポート(from my_app.utility import validate_user)で書きましょう。
実プロジェクト風の構成
ここまでのルールで、実際のアプリでよく見るフォルダ構成が組み立てられます。たとえば、設定・データベース・ユーティリティの 3 つのパッケージに分けた構成は次のようになります。
- main.py — 実行ファイル(直接 python で起動する側)
- config.py — アプリ全体の設定値
- __init__.py — my_app パッケージの公開 API
- __init__.py
- connection.py / models.py
- __init__.py
- validator.py / helper.py
main.pyからmy_app/パッケージ全体を読み込み、内部のモジュール同士は相対インポートで結ぶ。各サブフォルダにも__init__.pyを置いて、それぞれの公開 API をまとめる。main.py からはfrom my_app.utility import validate_userのように絶対インポートで公開関数を呼び、utility/の内側にある validator.py から helper.py を呼ぶときはfrom .helper import log_messageと相対インポートで書く、という役割分担になります。
ここからは応用 — つまずいたら戻ってきて OK
次の演習は2 つのパッケージを並行で組み立てる集大成課題です。__init__.py の再エクスポートが手に馴染んでいて、相対インポート / 絶対インポートの違いがあやしくない状態で進むのが理想です。詰まったら直前のvalidator.py演習や、上の「実プロジェクト風の構成」の図に戻って整理してから挑戦してください。
複数フォルダ構成にチャレンジ
ここまでのルールを使って、2 つのパッケージを並行で組み立てる演習に挑戦してみましょう。商品カタログを扱うcatalog/パッケージと、請求書を整形するbilling/パッケージを別々のフォルダで管理し、main.py から両方を呼び出して 1 つの注文処理を完成させます。役割をフォルダごとに分けると、後で「商品データだけ差し替えたい」「請求書の書式だけ変えたい」となっても影響範囲をそのフォルダ内に閉じ込められるのが利点です。
- main.py — 実行ファイル (両パッケージを使ってオーダー処理)
- __init__.py —
from .products import get_priceで再エクスポート - products.py —
get_price(name)を実装する
- __init__.py —
from .invoice import format_invoiceで再エクスポート - invoice.py —
format_invoice(name, qty, unit_price)を実装する
catalog/には商品データ (products.py)、billing/には請求書整形 (invoice.py) を置く。各__init__.pyが公開 API を再エクスポートし、main.py からはfrom catalog import get_priceとfrom billing import format_invoiceの 2 つの絶対インポートで両方を呼び出せる。__init__.pyが.products / .invoiceを相対インポートで再エクスポート。ここから先は補足 — 実務では稀
__all__ を扱う以下のセクションは情報目的の補足です。from package import *を実際に使う機会は実務でほとんどなく、本筋の理解には影響しません。「明示的に名前を書く」 import の使い方さえ押さえていれば、ここはサッと読んで進めても OK です。
__all__ で from パッケージ import * を制御する
from my_package import *のようにアスタリスクで全部読み込む書き方をされたとき、何を公開するかは__init__.py内の__all__で制御できます。__all__ = ["add"]と書いておくと、*インポートではaddだけが取り込まれ、他の関数は取り込まれません。
__all__ = ["add"]を __init__.py に書くと、from パッケージ import *で取り込まれるのは add のみ。multiply は除外される(明示的に名前を指定すれば取り込める)。# my_package/__init__.py
from .calculation import add, multiply
__all__ = ["add"] # * では add しか公開しない
# 利用側
# from my_package import *
# add(1, 2) # OK
# multiply(1, 2) # NameError(* では取り込まれていない)
実務では * インポートは避ける
from my_package import *は何が読み込まれたか一目で分からないため、実務ではほとんど使いません。from my_package import add, multiplyのように明示的に名前を書くのが基本です。__all__は「誰かが*を使った場合の保険」と捉えておけば十分です。
この記事では、__init__.pyで複数モジュールを 1 つのパッケージとして束ねる書き方、from パッケージ import 名前とimport パッケージの 2 通りの書き方の違い、絶対インポートと相対インポートの使い分け、そして実プロジェクトに近いフォルダ構成を確認しました。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2my_app/utility/validator.pyから、同じ utility フォルダ内のhelper.pyを読み込みたいとき、相対インポートとして正しい書き方はどれですか?
Q3実行ファイル(python main.pyで直接起動するファイル)の中で相対インポート(from . import xxx)を書くと、何が起きますか?