Python & AI Tutorials Logo
Python プログラミング

22. モジュールとパッケージでコードを整理する

Python プログラムが大きくなるにつれて、すべてのコードを 1 つのファイルに入れておくのは非現実的になります。関連する関数(function)、クラス(class)、変数(variable)を、別々のファイルに分けて整理し、異なるプログラム間で再利用できるようにしたくなるでしょう。Python の モジュール(module)パッケージ(package) の仕組みは、まさにこの機能—コードを効果的に整理し、共有し、再利用するための方法—を提供します。

この章では、Python の import システムがどのように動作するか、自分のモジュールを作成して使う方法、そして複数のモジュールをパッケージとして整理する方法を見ていきます。また、インポート可能なモジュールとしても単独のスクリプトとしても動作するファイルを書けるようにする、特別な __name__ 変数についても確認します。

22.1) モジュールとは何か、そして import はどう動くのか

モジュールの理解

モジュール(module) とは、定義や文を含む Python ファイルのことです。あなたが作成する任意の .py ファイルはモジュールです。calculator.py というファイルに関数を書けば、そのファイルは calculator という名前のモジュールになり、他の Python ファイルから利用できます。

モジュールには、いくつかの重要な目的があります:

  • コードの再利用性(Code Reusability): 関数を 1 回書けば、複数のプログラムで使える
  • 整理(Organization): 関連する機能をまとめられる
  • 名前空間の管理(Namespace Management): 衝突を避けるために名前を分離できる
  • 保守性(Maintainability): 小さく焦点を絞ったファイルは理解・変更しやすい

これがどう動くのかを見るために、簡単なモジュールを作ってみましょう。greetings.py というファイルを作成します:

python
# greetings.py
def say_hello(name):
    """Return a friendly greeting."""
    return f"Hello, {name}!"
 
def say_goodbye(name):
    """Return a farewell message."""
    return f"Goodbye, {name}. See you soon!"
 
# モジュールレベルの変数
default_greeting = "Welcome"

このファイルはこれでモジュールになりました。2 つの関数と 1 つの変数が含まれており、他の Python ファイルから利用できます。

import 文

モジュールのコードを使うには、それを インポート(import) します。import 文は、Python に対してモジュールを読み込み、その内容を利用可能にするよう指示します。同じディレクトリに main.py という別のファイルを作成します:

python
# main.py
import greetings
 
message = greetings.say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
farewell = greetings.say_goodbye("Bob")
print(farewell)  # Output: Goodbye, Bob. See you soon!
 
print(greetings.default_greeting)  # Output: Welcome

main.py を実行すると、Python は import greetings 文を実行します。裏側では次のことが起きています:

No

Yes

import greetings

greetings
はすでにインポート済み?

greetings.py を検索

greetings.py を
上から下へ実行

'greetings' という名前の
モジュールオブジェクトを作成

関数と変数を
属性として格納

現在の名前空間で
'greetings' を利用可能にする

既存の
モジュールオブジェクトを使用

重要: Python は、あるプログラム内でモジュールが最初にインポートされたときだけ、そのモジュールのコードを実行します。同じプログラム内での以降のインポートは、すでに読み込まれたモジュールを再利用します。これにより、重複実行を防ぎ、時間を節約できます。

モジュール内容へのアクセス

モジュールをインポートした後、その内容には ドット記法(dot notation) でアクセスします: module_name.item_name。これは、第 5 章と第 14 章で学んだ text.upper() のような文字列メソッドや、numbers.append() のようなリストメソッドへのアクセスと似ています。

python
import greetings
 
# 関数へのアクセス
result = greetings.say_hello("Charlie")
 
# 変数へのアクセス
greeting = greetings.default_greeting
 
# モジュールの中身を確認することもできます
print(dir(greetings))  # モジュールで定義されたすべての名前を一覧表示します

ドット記法により、各名前がどこから来たのかが明確になります。greetings.say_hello() を見れば、この関数が greetings モジュール由来だとすぐ分かります。

モジュール検索パス

import greetings と書いたとき、Python はどのようにして greetings.py を見つけるのでしょうか。Python は特定の順序でモジュールを検索します:

  1. 現在のディレクトリ(Current Directory): 実行しているスクリプトがあるディレクトリ
  2. PYTHONPATH: 環境変数 PYTHONPATH に列挙されたディレクトリ(設定されている場合)
  3. 標準ライブラリ(Standard Library): Python の組み込みモジュールディレクトリ
  4. site-packages: pip でインストールされたサードパーティパッケージ

sys.path を調べると、Python の検索パスを確認できます:

python
import sys
 
for path in sys.path:
    print(path)

Output (example - your actual paths will differ based on your system and Python installation):

/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packages

出力の最初のパスは現在の作業ディレクトリです。Python はこのディレクトリを最初に検索するため、同じディレクトリ内のモジュールを見つけられます。

モジュール名とファイル名

モジュール名は、ファイル名から .py 拡張子を除いたものです。ファイルが string_utils.py なら、モジュール名は string_utils です。モジュール名は Python の識別子ルール(第 3 章で学びました)に従う必要があります:

  • 文字またはアンダースコアで始まる
  • 文字、数字、アンダースコアのみを含む
  • Python のキーワードは使えない
python
# 有効なモジュール名(およびファイル名)
import data_processor      # data_processor.py
import user_auth          # user_auth.py
import _internal_helpers  # _internal_helpers.py
 
# 無効 - エラーの原因になります
# import 2d_graphics       # Can't start with digit
# import my-module         # Hyphens not allowed
# import class             # 'class' is a keyword

よくある落とし穴: 標準ライブラリモジュールのシャドーイング

標準ライブラリモジュールと同じ名前を自分のモジュールに付けないよう注意してください。プロジェクトディレクトリに random.py というファイルを作ると、Python は標準ライブラリの random モジュールではなくあなたのファイルをインポートしてしまい、混乱しやすいエラーの原因になります:

python
# あなたのファイル: random.py
def my_function():
    return 42
 
# プロジェクト内の別ファイル
import random
print(random.randint(1, 6))  # ERROR! Your random.py doesn't have randint()

これを避けるには、その名前が標準ライブラリですでに使われていないかを、モジュール作成前に確認してください。Python の対話シェルでインポートを試し、エラーなくインポートできるなら、その名前はすでに使われています。

import 中に起きること

モジュールをインポートすると実際に何が起きるのかを見てみましょう。demo_module.py というファイルを作成します:

python
# demo_module.py
print("Module is being loaded!")
 
def greet():
    print("Hello from demo_module")
 
print("Module loading complete!")

次に、これをインポートします:

python
# test_import.py
print("Before import")
import demo_module
print("After import")
 
demo_module.greet()

Output:

Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_module

demo_module.pyprint() 文が import 中に実行されることに注目してください。これは、モジュールをインポートするとトップレベルのコードがすべて実行されることを示しています。関数定義は後で使えるように保存されますが、関数の外にあるコードは即座に実行されます。

同じプログラム内で同じモジュールを再度インポートしても、読み込みメッセージはもう表示されません:

python
import demo_module  # First import - executes module code
import demo_module  # Second import - uses cached module
import demo_module  # Third import - still uses cached module

Output:

Module is being loaded!
Module loading complete!

モジュールのコードは、何回インポートしても 1 回しか実行されません。

22.2) import のいろいろな方法: import、from、as

Python には、モジュールやその内容をインポートする方法がいくつかあります。それぞれの方法で、インポートした名前へのアクセス方法や、名前空間への影響が異なります。

基本の import 文

すでに見てきた基本の import 文は、モジュール全体を読み込みます:

python
import math
 
result = math.sqrt(16)
print(result)  # Output: 4.0
 
pi_value = math.pi
print(pi_value)  # Output: 3.141592653589793

この方法では、常にモジュール名をプレフィックスとして使います。コードがとても明確になり、名前がどこから来たのかが常に分かります。

from による特定名のインポート

モジュールから 1 つか 2 つだけ必要な場合もあります。from 文を使うと、特定の名前を名前空間に直接インポートできます:

python
from math import sqrt, pi
 
result = sqrt(25)  # No 'math.' prefix needed
print(result)  # Output: 5.0
 
print(pi)  # Output: 3.141592653589793

これで sqrtpimath. プレフィックスなしで直接使えます。これらの名前を頻繁に使う場合に便利です。

greetings モジュールでも例を見てみましょう:

python
# Using from import
from greetings import say_hello
 
message = say_hello("Diana")  # Direct access
print(message)  # Output: Hello, Diana!
 
# However, say_goodbye is not available since we didn't import it
# say_goodbye("Diana")  # NameError: name 'say_goodbye' is not defined

1 つの文で複数の名前をインポートすることもできます:

python
from greetings import say_hello, say_goodbye, default_greeting
 
print(say_hello("Eve"))      # Output: Hello, Eve!
print(say_goodbye("Frank"))  # Output: Goodbye, Frank!
print(default_greeting)      # Output: Welcome

ワイルドカードインポート(そして避けるべき理由)

Python では * を使って、モジュールからすべてをインポートできます:

python
from math import *
 
print(sqrt(9))   # Output: 3.0
print(cos(0))    # Output: 1.0
print(pi)        # Output: 3.141592653589793

これは、モジュール内の公開名(アンダースコアで始まらない名前)をすべてインポートします。便利そうに見えますが、一般に 悪い習慣 とされています。理由は次のとおりです:

  1. 名前空間の汚染(Namespace Pollution): どの名前をインポートしているのか正確に分からない
  2. 名前衝突(Name Conflicts): インポートした名前が自分の変数を上書きするかもしれない
  3. 可読性(Readability): 読み手が名前の出どころを判断できない
python
# 問題が起きやすい例
from math import *
 
# 後でコード内に...
def sqrt(x):
    """Your own square root function."""
    return x ** 0.5
 
# どちらの sqrt を使っていますか? 自分のもの、それとも math のもの?
result = sqrt(16)  # Confusing!

ベストプラクティス: 特定の名前だけをインポートするか、基本の import 文を使ってください。対話セッションで試す場合を除き、from module import * は避けましょう。

as によるインポート名の変更

モジュール名や関数名が長い場合、または名前衝突を避けたい場合があります。as キーワードを使うと、別名(alias)を作れます:

python
import math as m
 
result = m.sqrt(36)
print(result)  # Output: 6.0

これは、長い名前のモジュールや、一般的な慣習に従う場合に特に便利です:

python
import datetime as dt
 
today = dt.date.today()
print(today)  # Output: 2025-12-19 (or current date)

特定のインポートに別名を付けることもできます:

python
from math import sqrt as square_root
 
result = square_root(49)
print(result)  # Output: 7.0

名前衝突がある場合に役立ちます:

python
from math import sqrt as math_sqrt
 
def sqrt(x):
    """Custom square root with input validation."""
    if x < 0:
        return None
    return math_sqrt(x)
 
print(sqrt(25))   # Output: 5.0 (your function)
print(sqrt(-4))   # Output: None (your function)

インポートスタイルの組み合わせ

同じファイル内で異なるインポートスタイルを混ぜられます:

python
import math
from datetime import date, time
from random import randint as random_int
 
# プレフィックス付きで math を使う
radius = 5
area = math.pi * radius ** 2
 
# date と time を直接使う
today = date.today()
current_time = time(14, 30)
 
# 別名を付けた関数を使う
dice_roll = random_int(1, 6)

適切なインポートスタイルの選び方

判断ガイドはこちらです:

import module を使うのは次のとき:

  • モジュールから複数の項目が必要
  • 名前の出どころを最大限明確にしたい
  • モジュール名が短く分かりやすい

from module import name を使うのは次のとき:

  • 特定の項目が 1 つか 2 つだけ必要
  • 名前が特徴的で衝突しにくい
  • その名前を頻繁に使う

import module as alias を使うのは次のとき:

  • モジュール名が非常に長い
  • 一般的な慣習に従う(例: import numpy as np)
  • 他モジュールとの衝突を避ける必要がある

本番コードでは from module import * を避ける:

  • 対話シェルでの手早い実験にだけ使う
  • 他者がインポートするモジュールでは決して使わない

良いインポートの実践例を示す、完全な例を見てみましょう:

python
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
 
def calculate_statistics(numbers):
    """Calculate various statistics for a list of numbers."""
    if not numbers:
        return None
    
    avg = mean(numbers)
    mid = median(numbers)
    std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
    
    return {
        'mean': avg,
        'median': mid,
        'std_dev': std_dev,
        'timestamp': dt.now()
    }
 
# Test the function
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}")      # Output: Mean: 30.0
print(f"Median: {stats['median']}")  # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}")  # Output: Std Dev: 14.14

この例が示していること:

  • import math はモジュール全体のため(後で別の math 関数も使うかもしれない)
  • from statistics import mean, median は頻繁に使う特定の関数のため
  • from datetime import datetime as dt はよく使われる別名のため

22.3) よく使う Python 標準ライブラリモジュールの概要

Python には豊富な 標準ライブラリ(standard library) が付属しており、よくあるプログラミング作業を解決するためのモジュール群が揃っています。これらのモジュールは常に利用可能で、追加でインストールする必要はありません。標準ライブラリに何があるかを理解しておくと、「車輪の再発明」を避けられます。

math モジュール

math モジュールは、基本的な算術演算を超える数学関数を提供します:

python
import math
 
# 三角関数
angle_rad = math.radians(45)  # 度をラジアンに変換
print(math.sin(angle_rad))    # Output: 0.7071067811865476
print(math.cos(angle_rad))    # Output: 0.7071067811865475
 
# 丸めと絶対値
print(math.ceil(4.2))   # Output: 5 (round up)
print(math.floor(4.8))  # Output: 4 (round down)
print(math.fabs(-7.5))  # Output: 7.5 (absolute value as float)
 
# 指数と対数
print(math.exp(2))      # Output: 7.38905609893065 (e^2)
print(math.log(100))    # Output: 4.605170185988092 (natural log)
print(math.log10(100))  # Output: 2.0 (base-10 log)
 
# 定数
print(math.pi)  # Output: 3.141592653589793
print(math.e)   # Output: 2.718281828459045

第 4 章で学んだとおり、math モジュールは高度な数学演算に不可欠です。

random モジュール

random モジュールは疑似乱数を生成し、ランダムな選択を行えます:

python
import random
 
# ランダム整数
dice = random.randint(1, 6)  # Random integer from 1 to 6 (inclusive)
print(f"Dice roll: {dice}")
 
# ランダム浮動小数
probability = random.random()  # Random float from 0.0 to 1.0
print(f"Probability: {probability:.4f}")
 
# シーケンスからランダムに 1 つ選ぶ
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
 
# リストをその場でシャッフル
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
 
# 重複なしのランダムサンプル
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")

Output (example - will vary due to randomness):

Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]

datetime モジュール

datetime モジュールは日付と時刻を扱います:

python
from datetime import date, time, datetime, timedelta
 
# 現在の日付と時刻
today = date.today()
now = datetime.now()
print(f"Today: {today}")  # Output: Today: 2025-12-19
print(f"Now: {now}")      # Output: Now: 2025-12-19 14:30:45.123456
 
# 特定の日付と時刻の作成
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
 
print(f"Birthday: {birthday}")          # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}")       # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}")    # Output: Appointment: 2025-12-25 10:00:00
 
# timedelta による日付計算
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}")    # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}")  # Output: Next week: 2025-12-26
 
# 構成要素の取り出し
print(f"Year: {today.year}")      # Output: Year: 2025
print(f"Month: {today.month}")    # Output: Month: 12
print(f"Day: {today.day}")        # Output: Day: 19

os モジュール

os モジュールはオペレーティングシステム機能を提供します。詳しくは第 26 章で扱いますが、ここではプレビューとして紹介します:

python
import os
 
# 現在の作業ディレクトリ
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
 
# ディレクトリ内のファイル一覧
files = os.listdir('.')
print(f"Files: {files[:3]}")  # 最初の 3 件を表示
 
# パスが存在するかチェック
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
 
# パス要素を結合(OS 間で動作)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}")  # Output: data/users/profile.txt (or data\users\profile.txt on Windows)

sys モジュール

sys モジュールはシステム固有のパラメータと関数を提供します:

python
import sys
 
# Python のバージョン情報
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
 
# プラットフォーム情報
print(f"Platform: {sys.platform}")  # Output: linux, darwin, win32, etc.
 
# 最大整数サイズ
print(f"Max int: {sys.maxsize}")

statistics モジュール

statistics モジュールは統計計算のための関数を提供します:

python
import statistics
 
grades = [85, 92, 78, 90, 88, 95, 82]
 
# 中心傾向
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
 
print(f"Mean: {avg}")      # Output: Mean: 87.14285714285714
print(f"Median: {mid}")    # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
 
# ばらつき
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
 
print(f"Standard deviation: {std_dev:.2f}")  # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}")           # Output: Variance: 34.81

collections モジュール

collections モジュールは、特化したコンテナ型を提供します。詳しくは第 39 章で扱いますが、ここでは一部を紹介します:

python
from collections import Counter, defaultdict
 
# Counter - 出現回数を数える
text = "hello world"
letter_counts = Counter(text)
print(letter_counts)  # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l'])  # Output: 3
 
# defaultdict - デフォルト値を持つ辞書
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists))  # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}

さらに標準ライブラリモジュールを探す

Python の標準ライブラリには 200 以上のモジュールがあります。いくつかの方法で探索できます:

python
# 利用可能なモジュールをすべて表示(少し時間がかかります)
help('modules')
 
# 特定モジュールのヘルプを表示
import math
help(math)
 
# モジュールの中身を確認
import random
print(dir(random))

Python のドキュメント(https://docs.python.org/3/library/) には、すべての標準ライブラリモジュールについての包括的な情報があります。経験を積むにつれて、どのモジュールが自分の作業に最も役立つかが分かってくるでしょう。

22.4) 自分のモジュールを作成して使う

自分のモジュールを作るのは簡単で、どんな Python ファイルもモジュールになれます。重要なのは、モジュールが焦点を絞っていて再利用でき、理解しやすいように、コードを思慮深く整理することです。

シンプルなモジュールを作る

学生の成績を扱うモジュールを作ってみましょう。grade_calculator.py というファイルを作成します:

python
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
 
def calculate_average(grades):
    """Calculate the average of a list of grades."""
    if not grades:
        return 0
    return sum(grades) / len(grades)
 
def get_letter_grade(numeric_grade):
    """Convert a numeric grade to a letter grade."""
    if numeric_grade >= 90:
        return 'A'
    elif numeric_grade >= 80:
        return 'B'
    elif numeric_grade >= 70:
        return 'C'
    elif numeric_grade >= 60:
        return 'D'
    else:
        return 'F'
 
def find_highest(grades):
    """Find the highest grade in a list."""
    if not grades:
        return None
    return max(grades)
 
def find_lowest(grades):
    """Find the lowest grade in a list."""
    if not grades:
        return None
    return min(grades)
 
# モジュールレベルの定数
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90

次に、このモジュールを使う別のファイルを作成します:

python
# student_report.py
import grade_calculator
 
# 学生のテスト得点
test_scores = [85, 92, 78, 88, 95]
 
# 統計値を計算
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
 
# レポートを生成
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
 
# 成績優秀者かチェック
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
    print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
    print("Status: Passing")
else:
    print("Status: Needs Improvement")

Output:

Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: Passing

モジュールのドキュメント

grade_calculator.py の先頭にある docstring に注目してください。このモジュールレベルの docstring は、そのモジュールが何をするかを説明します。誰かが help() を使うと表示されます:

python
import grade_calculator
help(grade_calculator)

これにより、モジュール docstring とすべての関数 docstring を含む、モジュールのドキュメントが表示されます。良いドキュメントは、モジュールを使いやすくします。

モジュールレベル変数と定数

モジュールには、そのモジュールを使うすべての箇所で共有される変数を含められます。これらは設定や定数に使われることがよくあります:

python
# config.py
"""Application configuration settings."""
 
# データベース設定
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
 
# アプリケーション設定
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800  # seconds
DEBUG_MODE = False
 
# ファイルパス
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
 
# 機能フラグ
ENABLE_CACHING = True
ENABLE_LOGGING = True

モジュールから設定を使う例:

python
# app.py
import config
 
def connect_database():
    """Connect to the database using config settings."""
    print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
    print(f"Database: {config.DB_NAME}")
    
    if config.DEBUG_MODE:
        print("DEBUG: Connection details logged")
 
def check_login_attempts(attempts):
    """Check if login attempts exceed the limit."""
    if attempts >= config.MAX_LOGIN_ATTEMPTS:
        print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
        return False
    return True
 
connect_database()
print(check_login_attempts(2))  # Output: True
print(check_login_attempts(4))  # Output: Too many attempts! Maximum is 3

Output:

Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3

重要: モジュールレベルの変数は、すべてのインポートで共有されます。モジュール変数を変更すると、その変更はそのモジュールを使うすべてのコードに影響します:

python
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
 
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}")  # Will be True!

この挙動は便利な場合もありますが、意外に感じることもあります。モジュールレベルの変数を変更する場合は注意しましょう。

モジュール内のプライベート名

慣習として、アンダースコアで始まる名前は、そのモジュールの プライベート(private) または 内部(internal) とみなされます:

python
# user_manager.py
"""Module for managing user accounts."""
 
# プライベートなヘルパー関数
def _validate_email(email):
    """Internal function to validate email format."""
    return '@' in email and '.' in email
 
# 公開関数
def create_user(username, email):
    """Create a new user account."""
    if not _validate_email(email):
        return None
    
    user = {
        'username': username,
        'email': email,
        'active': True
    }
    return user
 
# プライベート定数
_MAX_USERNAME_LENGTH = 20
 
# 公開定数
MIN_PASSWORD_LENGTH = 8

from user_manager import * を使うと、プライベート名(アンダースコアで始まるもの)はインポートされません。ただし必要なら、明示的にアクセスすることはできます:

python
import user_manager
 
# 公開関数 - 利用が意図されている
user = user_manager.create_user("alice", "alice@example.com")
 
# プライベート関数 - アクセスはできるが頼るべきではない
# (将来のバージョンで変更される可能性があります)
is_valid = user_manager._validate_email("test@test.com")

アンダースコアの接頭辞は、他のプログラマに対する合図です: 「これは実装詳細です。同じままであることを前提にしないでください。」

22.5) パッケージと __init__.py の理解

プロジェクトが大きくなると、複数の関連モジュールを パッケージ(package) に整理したくなるでしょう。パッケージとは、Python モジュールと特別な __init__.py ファイルを含むディレクトリのことです。

パッケージとは何か

パッケージ(package) は、複数のモジュールを階層構造に整理する方法です。Python ファイルを含むフォルダであり、フォルダ自体をインポートできるものだと考えてください。

シンプルなパッケージ構造は次のとおりです:

myproject/
    main.py
    utilities/
        __init__.py
        text.py
        math.py
        file.py

この構造では、utilities がパッケージであり、textmathfile の 3 つのモジュールを含みます。__init__.py ファイル(空でもよい)は、utilities がパッケージであることを Python に知らせます。

シンプルなパッケージを作る

データ処理用のパッケージを作ってみましょう。まず、次のディレクトリ構造を作成します:

data_tools/
    __init__.py
    validators.py
    formatters.py

validators.py を作成します:

python
# data_tools/validators.py
"""Data validation functions."""
 
def is_valid_email(email):
    """Check if email has basic valid format."""
    return '@' in email and '.' in email.split('@')[1]
 
def is_valid_phone(phone):
    """Check if phone number has valid format (simple check)."""
    digits = ''.join(c for c in phone if c.isdigit())
    return len(digits) == 10
 
def is_positive_number(value):
    """Check if value is a positive number."""
    try:
        return float(value) > 0
    except (ValueError, TypeError):
        return False

formatters.py を作成します:

python
# data_tools/formatters.py
"""Data formatting functions."""
 
def format_phone(phone):
    """Format phone number as (XXX) XXX-XXXX."""
    digits = ''.join(c for c in phone if c.isdigit())
    if len(digits) != 10:
        return phone
    return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
 
def format_currency(amount):
    """Format number as currency."""
    return f"${amount:,.2f}"
 
def format_percentage(value, decimals=1):
    """Format number as percentage."""
    return f"{value * 100:.{decimals}f}%"

空の __init__.py を作成します:

python
# data_tools/__init__.py
"""Data processing tools package."""

パッケージからインポートする

これで、いくつかの方法でパッケージからインポートできます:

python
# Method 1: Import the module from the package
import data_tools.validators
 
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}")  # Output: Email valid: True
 
# Method 2: Import specific module with from
from data_tools import formatters
 
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}")  # Output: Formatted phone: (123) 456-7890
 
# Method 3: Import specific functions
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
 
print(is_valid_phone("555-1234"))  # Output: False (not 10 digits)
print(format_currency(1234.56))    # Output: $1,234.56

__init__.py ファイル

__init__.py ファイルには 2 つの目的があります:

  1. ディレクトリをパッケージとしてマークする: Python は __init__.py があるディレクトリをパッケージとして認識する
  2. パッケージの初期化コード: __init__.py 内のコードは、パッケージが最初にインポートされたときに実行される

__init__.py は空でも構いませんが、パッケージを使いやすくするためのコードを含めることもできます:

python
# data_tools/__init__.py
"""Data processing tools package."""
 
# よく使う関数をパッケージ名前空間にインポート
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
 
# パッケージバージョン
__version__ = '1.0.0'
 
# パッケージレベルの定数
DEFAULT_CURRENCY_SYMBOL = '$'

これで、利用者はパッケージから直接インポートできます:

python
# Instead of: from data_tools.validators import is_valid_email
# You can write:
from data_tools import is_valid_email, format_currency
 
print(is_valid_email("test@test.com"))  # Output: True
print(format_currency(99.99))           # Output: $99.99

22.6) __name__ 変数と if __name__ == "__main__": ファイルをスクリプトとして実行する

Python ファイルには 2 つの役割があります。モジュールとしてインポートされることもあれば、単独のスクリプトとして実行されることもあります。特別な __name__ 変数を使うと、どちらの状況でもうまく動くコードを書けます。

__name__ の理解

すべての Python モジュールには __name__ という組み込み変数があります。Python は、ファイルの使われ方に応じてこの変数を異なる値に設定します:

  • インポートされたとき: __name__ はモジュール名に設定される
  • 直接実行されたとき: __name__"__main__" に設定される

実際に見てみましょう。demo_name.py というファイルを作成します:

python
# demo_name.py
print(f"The __name__ variable is: {__name__}")

次に、これを直接実行します:

bash
python demo_name.py

Output:

The __name__ variable is: __main__

次に、別のファイルからインポートします:

python
# test_import.py
import demo_name

Output:

The __name__ variable is: demo_name

demo_name.py を直接実行すると、Python は __name__"__main__" に設定します。インポートすると、Python は __name__ をモジュール名("demo_name")に設定します。

if __name__ == "__main__": パターン

この挙動により、インポート時ではなく、ファイルが直接実行されたときにだけ動くコードを書けます。これは次のパターンで実現します:

python
if __name__ == "__main__":
    # Code here runs only when file is executed directly
    pass

なぜこれが便利なのかを示します。math_utils.py を作成します:

python
# math_utils.py
"""Utility functions for mathematical operations."""
 
def calculate_area(radius):
    """Calculate the area of a circle."""
    return 3.14159 * radius ** 2
 
def calculate_circumference(radius):
    """Calculate the circumference of a circle."""
    return 2 * 3.14159 * radius
 
# テストコード - ファイルが直接実行されたときだけ動きます
if __name__ == "__main__":
    print("Testing math_utils functions...")
    
    test_radius = 5
    area = calculate_area(test_radius)
    circumference = calculate_circumference(test_radius)
    
    print(f"Radius: {test_radius}")
    print(f"Area: {area:.2f}")
    print(f"Circumference: {circumference:.2f}")

このファイルを直接実行すると:

bash
python math_utils.py

Output:

Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42

しかし、インポートすると:

python
# use_math_utils.py
import math_utils
# テストコードは実行されません!
 
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}")  # Output: Area of circle: 314.16

if __name__ == "__main__": ブロック内のテストコードは、インポート時には実行されません。これにより、インポートする側に影響を与えずに、モジュール内にテストコード・例・デモを含められます。

if __name__ == "__main__" のよくある用途:

テストとデモ

モジュールの使い方を示す例を含めます:

python
# string_tools.py
def reverse_string(text):
    """Reverse a string."""
    return text[::-1]
 
def count_vowels(text):
    """Count vowels in text."""
    vowels = 'aeiouAEIOU'
    return sum(1 for char in text if char in vowels)
 
if __name__ == "__main__":
    # デモ用コード
    sample = "Hello, World!"
    
    print(f"Original: {sample}")
    print(f"Reversed: {reverse_string(sample)}")
    print(f"Vowels: {count_vowels(sample)}")

この章では、モジュールとパッケージを使って Python コードを整理する方法を学びました。import システムの動作、コードをインポートするさまざまな方法、自分のモジュールやパッケージの作り方も確認しました。また、__name__ 変数と、ファイルをインポート可能なモジュールとしても単独スクリプトとしても動作させる if __name__ == "__main__": パターンについても学びました。

これらの整理のための道具は、プログラムが大きくなるほど重要になります。次の章では、関数をデータとして使い、シンプルな関数型プログラミング手法を適用する方法を、ここで確立したコード整理の堅固な基盤の上に積み上げながら探っていきます。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai