■パッケージとは?
sample-package/ ← パッケージ(ディレクトリ)
├── __init__.py ← 「これはパッケージです」という目印
├── math_utils.py ← モジュール(.pyファイル)
├── file_utils.py ← モジュール(.pyファイル)
└── network/ ← サブパッケージ
├── __init__.py ← サブパッケージの目印
└── http_client.py ← モジュール(.pyファイル)
複数のファイルをまとめてディレクトリ管理して、他のファイルから利用しやすくしたものを「パッケージ」と言います。
一つのファイルで全てのコードを書いて完結することはなく、複数ファイルに分けて処理を書いて「import」という処理を行って、そのファイルに書いてある関数を別のファイルで利用したり、別のファイルで書いてあるクラスを、他の関数で利用してクラスのインスタンスを作成したり、そういうことをすることがあります。
- モジュール
- 1つのpythonファイル
- パッケージ
- init.pyを含むディレクトリ
- ライブラリ
- 複数のパッケージやモジュールの集合
■sys.path
import sys
print(sys.path) # importする対象のファルダ
# 実行結果
[
'/Users/username/sample', # 実行ファイルのディレクトリ
'/Users/username/.asdf/installs/python/3.13.7t/lib/python313t.zip',
'/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t', # 標準ライブラリが格納されているディレクトリ
'/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t/lib-dynload',
'/Users/username/sample/.venv/lib/python3.13t/site-packages' # 外部ライブラリでインストール済みパッケージのディレクトリ
]
Pythonパッケージをimportした場合、Pythonがモジュールやパッケージを探すときに検索するディレクトリパスのリストを出力することができます。
import文を実行すると、Pythonはsys.pathに含まれるディレクトリを順番に調べて、該当するファイルやパッケージを見つけます。
■sys.modules
import sys
print(sys.modules.keys())
import math
print(sys.modules['math'])
# 結果
{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_io': <module '_io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'posix': <module 'posix' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, 'time': <module 'time' (built-in)>, 'zipimport': <module 'zipimport' (frozen)>, '_codecs': <module '_codecs' (built-in)>, 'codecs': <module 'codecs' (frozen)>, 'encodings.aliases': <module 'encodings.aliases' from '/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t/encodings/aliases.py'>, 'encodings': <module 'encodings' from '/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t/encodings/__init__.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from '/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t/encodings/utf_8.py'>, '_signal': <module '_signal' (built-in)>, '_abc': <module '_abc' (built-in)>, 'abc': <module 'abc' (frozen)>, 'io': <module 'io' (frozen)>, '__main__': <module '__main__' from '/Users/username/workspace/github.com/user-name/sample/05_package/main.py'>, '_stat': <module '_stat' (built-in)>, 'stat': <module 'stat' (frozen)>, '_collections_abc': <module '_collections_abc' (frozen)>, 'errno': <module 'errno' (built-in)>, 'genericpath': <module 'genericpath' (frozen)>, 'posixpath': <module 'posixpath' (frozen)>, 'os.path': <module 'posixpath' (frozen)>, 'os': <module 'os' (frozen)>, '_sitebuiltins': <module '_sitebuiltins' (frozen)>, 'site': <module 'site' (frozen)>}
<module 'math' from '/Users/username/.asdf/installs/python/3.13.7t/lib/python3.13t/lib-dynload/math.cpython-313t-darwin.so'>
一度読み込まれたモジュールをメモリに保存しておくキャッシュ辞書がsys.modulesです。
モジュール名をキー、モジュールオブジェクトを値として保存し、同じモジュールを再度importするときは実行せずにこのキャッシュから取得する。
1.import時: sys.pathを順番に検索してモジュール発見する
2.初回読み込み: モジュールを実行してsys.modulesにキャッシュする
3.2回目以降: sys.modulesから既存オブジェクトを返す
import math
print(math.ceil(4.2))
import numpy
# 結果
Traceback (most recent call last):
File "/Users/username/sample/main.py", line 5, in <module>
import numpy
ModuleNotFoundError: No module named 'numpy'
import numpyでnumpyをモジュール内で使おうとしていますが、外部ライブラリをインストールしていないので、ModuleNotFoundError: No module named 'numpy'というエラーになります。
❯ pip install numpy
Collecting numpy
Downloading numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl.metadata (62 kB)
Downloading numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl (5.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.2/5.2 MB 68.1 MB/s 0:00:00
Installing collected packages: numpy
Successfully installed numpy-2.3.5
pip install numpy でインストールすると、'/Users/username/sample/.venv/lib/python3.13t/site-packages'に外部ライブラリがインストールされます。
import sys
import math
print(math.ceil(4.2))
import numpy
print(sys.modules) # importしたライブラリ一覧
# 結果
{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, ... 省略
'numpy': <module 'numpy' from '/Users/username/sample/.venv/lib/python3.13t/site-packages/numpy/__init__.py'>
}
numpyをインストールしたので、sys.modulesの結果を出力してみます。
すると、/Users/username/sample/.venv/lib/python3.13t/site-packages/numpy/__init__.pyから読み込まれていることがわかります。
■別のファイルの機能をimportする
def add(a, b):
return a + b
def subtract(a, b):
return a - b
print("math_utils module loaded")
# math_utils.pyをimportして実行して、メモリにロードする
import math_utils # <module 'math_utils' from '/Users/username/sample/math_utils.py'>
print(math_utils)
print(math_utils.add(3, 5))
print(math_utils.subtract(10, 4))
import math_utilsでmath_utils.pyをimportして、メモリ上に格納しています。
addなどのメソッドを実行することで、外部ファイルを読み込んで処理を実行できます。
■importとfrom importのimport時の動作
◆ importの内部メカニズム
Pythonでimportを実行すると、内部では次のことが起きます。
- モジュールのコードを実行して定義をメモリに配置
-
sys.modulesにモジュールへの参照を登録(キャッシュとして機能) - 現在の名前空間に参照を追加
この3番目の「名前空間への追加のされ方」が、importとfrom importの本質的な違いです。
◆プロジェクト構成
sample/
├── main.py
└── my_package/
├── __init__.py
└── math_utils.py
◆import文
# my_package/math_utils.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
print("math_utilsが読み込まれました")
# my_package/__init__.py
print("my_packageが初期化されました")
# main.py
import sys
print("=== import前 ===")
print("my_package存在:", 'my_package' in sys.modules) # False
print("my_package.math_utils存在:", 'my_package.math_utils' in sys.modules) # False
print("\n=== import実行 ===")
import my_package.math_utils
# my_packageが初期化されました
# math_utilsが読み込まれました
print("\n=== import後 ===")
print("my_package存在:", 'my_package' in sys.modules) # True
print("my_package.math_utils存在:", 'my_package.math_utils' in sys.modules) # True
print("\n=== 登録された内容 ===")
print(sys.modules['my_package'])
# <module 'my_package' from '/Users/username/sample/my_package/__init__.py'>
print(sys.modules['my_package.math_utils'])
# <module 'my_package.math_utils' from '/Users/username/sample/my_package/math_utils.py'>
print("\n=== 関数の実行 ===")
print(my_package.math_utils.add(10, 5))
main.pyでimport my_package.math_utilsでimportしています。
import前とimport後のsys.modulesの結果を見ると、import後が登録されていることがわかります。
また、importを実行したときに、my_package/__init__.pyやmy_package/math_utils.pyのprint文が実行されていることがわかります。
内部動作として、
-
import my_package.math_utilsでimport文を実行する -
__init__.pyを実行する -
math_utils.pyを実行 -
sys.modulesに、以下のように登録される。
sys.modules = { 'my_package': <module 'my_package'>, 'my_package.math_utils': <module 'math_utils'> }◆from import文
# my_package/math_utils.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
print("math_utilsが読み込まれました")
# my_package/__init__.py
print("my_packageが初期化されました")
# main.py
import sys
print("=== import前 ===")
print("my_package存在:", 'my_package' in sys.modules) # False
print("my_package.math_utils存在:", 'my_package.math_utils' in sys.modules) # False
print("\n=== import実行 ===")
from my_package.math_utils import add
# my_packageが初期化されました
# math_utilsが読み込まれました
print("\n=== import後 ===")
print("my_package存在:", 'my_package' in sys.modules) # True
print("my_package.math_utils存在:", 'my_package.math_utils' in sys.modules) # True
print("\n=== 登録された内容 ===")
print(sys.modules['my_package'])
# <module 'my_package' from '/Users/username/sample/my_package/__init__.py'>
print(sys.modules['my_package.math_utils'])
# <module 'my_package.math_utils' from '/Users/username/sample/my_package/math_utils.py'>
print("\n=== 関数の実行 ===")
print(add(10, 5))
import文とfrom import文を比較すると、特に目立った差はありません。
1つあるとすれば、import文ではmy_package.math_utils.add(10, 5)で呼び出すのか、from import文ではadd(10, 5)で呼び出すのかの違いです。
これは、「名前空間に何を登録するか」の違いになります。
◆「名前空間」とは何か
名前空間とは、変数名と実際のオブジェクトを紐づけて登録する辞書のようなものです。
# main.py
x = 10
name = "Python"
# この時点での名前空間(簡略化)# {'x': 10, 'name': 'Python'}
print(dir()) # 現在の名前空間にある名前の一覧
importを実行すると、この名前空間に新しい名前が追加されます。
sys.modulesと名前空間の違いは以下になります。この2つは別物です。
| sys.modules | 名前空間 | |
|---|---|---|
| 役割 | 読み込んだモジュールのキャッシュ | 現在のスコープで使える名前の一覧 |
| スコープ | Python全体で共有 | ファイルや関数ごとに独立 |
| importで起きること | モジュールが登録される | 使うための参照が登録される |
●import文の場合のアクセス
# main.py
import my_package.math_utils
sys.modulesへの登録(Python全体のキャッシュ)
sys.modules = {
'my_package': <module 'my_package'>,
'my_package.math_utils': <module 'math_utils'>,
# ... 他のモジュール
}
名前空間への登録(main.pyで使える名前)
# main.pyの名前空間
{
'my_package': <module 'my_package'> # ← これだけ登録される
}
なぜmy_packageだけかというと、import my_package.math_utilsは「my_packageという名前で、パッケージへの参照を登録する」という意味だからです。
math_utilsへはmy_packageの属性としてアクセスします。
*# アクセスの流れ*
my_package # 名前空間から取得
.math_utils # my_packageの属性として取得
.add(10, 5) # math_utilsの属性として取得
●from import文の場合のアクセス
# main.py
from my_package.math_utils import add, subtract
sys.modulesへの登録(import文と同じ)
sys.modules = {
'my_package': <module 'my_package'>,
'my_package.math_utils': <module 'math_utils'>,
}
名前空間への登録(ここが違う)
# main.pyの名前空間
{
'add': <function add>, # 関数への直接参照
'subtract': <function subtract> # 関数への直接参照
}
my_packageもmath_utilsも名前空間には登録されません。
# アクセスの流れ
add(10, 5) # 名前空間から直接取得して実行
図解: 詳細版
┌─────────────────────────────────────────────────────────────────────┐
│ sys.modules(共通) │
│ Python全体で共有されるキャッシュ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 'my_package' ─────────→ <module 'my_package'> │ │
│ │ │ │ │
│ │ └─ __init__.py の内容 │ │
│ │ │ │
│ │ 'my_package.math_utils' → <module 'math_utils'> │ │
│ │ │ │ │
│ │ ├─ add: <function add at 0x...> │ │
│ │ │ │ │
│ │ └─ subtract: <function subtract> │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ importの種類によって
│ 登録のされ方が変わる
▼
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 【import my_package.math_utils の場合】 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ main.py の名前空間 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 'my_package' ──→ sys.modules['my_package'] への参照 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 使い方: my_package.math_utils.add(10, 5) │
│ ^^^^^^^^^^ │
│ 名前空間にあるのはここだけ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 【from my_package.math_utils import add, subtract の場合】 │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ main.py の名前空間 │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 'add' ──→ <function add> への直接参照 │ │ │
│ │ │ 'subtract' ──→ <function subtract> への直接参照 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 使い方: add(10, 5) │
│ ^^^ │
│ 名前空間から直接呼び出せる │
│ │
└─────────────────────────────────────────────────────────────────────┘
●コードで確認
# main.py
import sys
print("=== import文の場合 ===")
import my_package.math_utils
print("名前空間の内容:")
print(" my_package:", 'my_package' in dir()) # True
print(" math_utils:", 'math_utils' in dir()) # False
print(" add:", 'add' in dir()) # False
print("\nsys.modulesの内容:")
print(" my_package:", 'my_package' in sys.modules) # True
print(" my_package.math_utils:", 'my_package.math_utils' in sys.modules) # True
print("\nアクセス方法:")
print(" my_package.math_utils.add(10, 5) =", my_package.math_utils.add(10, 5))
# main.py
import sys
print("=== from import文の場合 ===")
from my_package.math_utils import add, subtract
print("名前空間の内容:")
print(" my_package:", 'my_package' in dir()) # False
print(" math_utils:", 'math_utils' in dir()) # False
print(" add:", 'add' in dir()) # True
print(" subtract:", 'subtract' in dir()) # True
print("\nsys.modulesの内容:")
print(" my_package:", 'my_package' in sys.modules) # True(登録はされている)
print(" my_package.math_utils:", 'my_package.math_utils' in sys.modules) # True
print("\nアクセス方法:")
print(" add(10, 5) =", add(10, 5))
名前空間の内容を確認すると、importとfrom importで名前空間への登録の仕方が違うことがわかります。
●名前空間の登録違いで何が良いのか?
# from importは名前の衝突が起きやすい
from my_package.math_utils import add
from another_package import add # 上書きされてしまう!
# importなら衝突しない
import my_package.math_utils
import another_package
my_package.math_utils.add(10, 5) # 明確に区別できる
another_package.add(10, 5)
from importで関数などをimportすると、同じ名前の関数を別モジュールから読み込んだ際に、上書きされてしまいます。
それに対して、importで異なるモジュールの同じ名前のメソッドを読み込んでも、名前空間が別なので上書きされることはありません。
また、importだとmy_package.math_utils.add()と書けるので、どのモジュールの関数なのかがひと目で分かります。
■実行ファイルと読み込みファイルの違い(name)
Pythonでは、「直接実行するファイル」と「importなどで読み込まれるファイル」に分かれます。
「直接実行するファイル」の「name」には「main」が入っています。
「importなどで読み込まれるファイル」には、ファイル名などが入っています。
◆直接実行するファイルの確認
print(__name__)
# 実行
❯ python main.py
__main__
python main.pyでファイルを直接実行して、__name__の中身を確認すると、__main__が代入されています。
◆importなどで読み込まれるファイル
import my_package.math_utils
print(__name__)
def add(a, b):
return a + b
def subtract(a, b):
return a - b
print(f"math_utilsが読み込まれました: {__name__}")
# 実行結果
❯ python main.py
math_utilsが読み込まれました: my_package.math_utils
__main__
my_package/math_utils.pyで出力している__name__は、my_package.math_utilsと出力されています。
これが、「直接実行するファイル」と「importなどで読み込まれるファイル」の違いです。
import my_package.math_utils
def main():
print("main関数が実行されました")
result1 = my_package.math_utils.add(5, 3)
result2 = my_package.math_utils.subtract(10, 4)
print(f"5 + 3 = {result1}")
print(f"10 - 4 = {result2}")
# ファイルを直接実行したときにのみ動作するコード
if __name__ == "__main__":
main()
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# 実行結果
❯ python main.py
main関数が実行されました
5 + 3 = 8
10 - 4 = 6
なので、特定のファイルを直接呼び出ひたときだけ実行したい処理は、上のようにif __name__ == "__main__":で分岐することができます。
■ init.pyの絶対インポートと相対インポートについて
◆__init__.pyでモジュールの出力を制御する
from my_package import add, subtract
result1 = add(5, 3)
result2 = subtract(10, 4)
print(f"5 + 3 = {result1}")
print(f"10 - 4 = {result2}")
from .math_utils import add
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# 実行結果
❯ python main.py
Traceback (most recent call last):
File "/Users/username/main.py", line 1, in <module>
from my_package import add, subtract
ImportError: cannot import name 'subtract' from 'my_package' (/Users/username/my_package/__init__.py)
main.pyでfrom my_package import add, subtractと書いて、add, subtractを読み込んでいます。
しかし、importしたときに実行される__init__.pyではfrom .math_utils import addでadd関すしか読み込んでいないです。
この状態で、main.pyを実行すると、ImportErrorが発生します。
__init__.pyでは、どのファイルのどの関数などだけをモジュールとして読み込んで使えるようにするかを制御することができます。
◆__all__でモジュールの出力を制御する
from my_package import *
result1 = add(5, 3)
result2 = subtract(10, 4)
print(f"5 + 3 = {result1}")
print(f"10 - 4 = {result2}")
from .math_utils import add, subtract
# from my_package import * で何が含まれるかを制御
__all__ = ['add']
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# 実行結果
❯ python main.py
Traceback (most recent call last):
File "/Users/username/main.py", line 4, in <module>
result2 = subtract(10, 4)
^^^^^^^^
NameError: name 'subtract' is not defined
__init__.pyの__all__ = ['add']でadd関数だけを外部から読み込めるようにモジュールを制御しています。
main.pyでfrom my_package import *で読み込むと、__all__に指定したモジュールだけ読み込めるので、subtractは読み込めないでNameErrorが発生します。
■相対インポートと絶体インポート
◆プロジェクト構成とファイルの内容
sample/
├── main.py ← 実行ファイル
└── my_package/
├── __init__.py
├── core.py ← ここから他をインポート
├── utils.py
├── config.py
└── subpackage/
├── __init__.py
└── advanced.py
def calculate(x, y):
return x + y
print("utils.py が読み込まれました")
DATABASE_URL = "sqlite:///app.db"
DEBUG = True
print("config.py が読み込まれました")
def advanced_calculation(x, y, z):
return x * y + z
print("advanced.py が読み込まれました")
◆絶対インポート
プロジェクトのルートディレクトリ(sys.pathに含まれるパス)からの完全なパスを指定する方法です。
# 絶対インポート: プロジェクトルートからの完全パス
import mypackage.utils
from mypackage.subpackage import advanced
from mypackage.config import DATABASE_URL
def main():
# utils.pyの関数を使用
result1 = mypackage.utils.calculate(10, 5)
print(f"計算結果1: {result1}")
# advanced.pyの関数を使用
result2 = advanced.advanced_calculation(2, 3, 4)
print(f"計算結果2: {result2}")
# configから定数を使用
print(f"データベースURL: {DATABASE_URL}")
import sys
print("sys.pathの最初の要素:", sys.path[0]) # `sys.path[0]`は通常、実行したスクリプトのあるディレクトリ が自動的に設定されます。
print()
# 絶対インポートでcoreモジュールを読み込む
from mypackage import core
core.main()
### 実行結果
sys.pathの最初の要素: /Users/username/sample
utils.py が読み込まれました
advanced.py が読み込まれました
config.py が読み込まれました
計算結果1: 15
計算結果2: 10
データベースURL: sqlite:///app.db
絶体importでは、sys.path[0]の出力結果(main.pyの親ディレクトリのsampleディレクトリ)を基準にしたパスの指定をします。
絶対インポートのメリット
- 明確で読みやすい: どこからインポートしているか一目瞭然
- 移動に強い: ファイルを移動してもインポート文を変更しなくて済む場合が多い
- IDEのサポートが良い: 補完やジャンプ機能が効きやすい
絶対インポートのデメリット
- 長くなりがち: パッケージ名が長いと冗長になる
- パッケージ名変更に弱い: パッケージ名を変えると全て修正が必要
◆相対インポート
現在のモジュールの位置を基準にして相対的なパスを指定する方法です。
相対インポートの記法
| 記法 | 意味 |
|---|---|
from . import module |
同じディレクトリのmodule |
from .. import module |
親ディレクトリのmodule |
from .subdir import module |
同じ階層のサブディレクトリのmodule |
from ..sibling import module |
親の兄弟ディレクトリのmodule |
# 相対インポート: 現在のモジュール位置を基準にする
from . import utils # 同ディレクトリのutils.py
from .subpackage import advanced # サブディレクトリのadvanced.py
from .config import DATABASE_URL # 同ディレクトリのconfig.py
def main():
# utils.pyの関数を使用
result1 = utils.calculate(10, 5)
print(f"計算結果1: {result1}")
# advanced.pyの関数を使用
result2 = advanced.advanced_calculation(2, 3, 4)
print(f"計算結果2: {result2}")
# configから定数を使用
print(f"データベースURL: {DATABASE_URL}")
# 実行結果
sys.pathの最初の要素: /Users/username/sample
utils.py が読み込まれました
advanced.py が読み込まれました
config.py が読み込まれました
計算結果1: 15
計算結果2: 10
データベースURL: sqlite:///app.db
相対パスでは、import文を書いているファイルを基準にします。
今回の場合は、mypackage/core.pyから見た相対パスで記載します。
相対インポートのメリット
- 短く書ける: パッケージ名を省略できる
- パッケージ名変更に強い: パッケージ名を変えても修正不要
- パッケージ内の独立性: パッケージを別プロジェクトに移植しやすい
相対インポートのデメリット
- スクリプトとして直接実行できない:
python core.pyではエラーになる - 読みにくい場合がある:
..が多いと構造が分かりにくい
