
Table of Contents(目次)
Pythonの多言語化を実現するパッケージ構成の作り方
この章では、これまで作成してきた「Fortune Cookie(フォーチュン・クッキー)」アプリをさらに整備していきます。
現時点では、fortune.pyはかなり構造化されたコードとなっています。しかし、対応言語が増えると、『言語コードに応じて読み込むモジュールを切り替える』処理を追加または修正する必要が生じます。
# 言語コードに応じて読み込むモジュールを切り替える
if lang_code == 'en':
import message.language_en as lang
else: # 'jp' or else
import message.language_jp as lang
そこで、この管理ロジックをmanager/language_manager.pyに分けます。ファイルを分けることで、変更の影響範囲を小さくし、安全に修正できるようになります。。
- 文言の修正があるとき:翻訳者や内容を変更したい人は
message/language_**.pyを編集すればよいので、fortune.pyのコードロジックに誤って手を加えるリスクが減ります。 - 新しい言語を追加したいとき:新しく
language_**.pyを作り、language_manager.pyに追加するだけで済みます。fortune.pyを編集する必要はありません。 - テストがしやすい:言語ごとのデータや選択ロジックを個別に単体テストできるので、不具合の切り分けが容易です。
まとめると、分けることで読みやすさ・安全性・拡張性・チーム開発の効率が向上します。これがパッケージ化・責務の分離(Single Responsibility Principle)の本質です。
Pythonでのパッケージ構成とモジュール分割の最終形
最終的に、message/が「文言や運勢のデータを格納する場所」、manager/が「選択・設定などの管理ロジックをまとめる場所」という構成にします。
fortune_cookie/
├─ fortune.py
├─ message/
│ ├─ __init__.py
│ ├─ language_en.py
│ └─ language_jp.py
└─ manager/
├─ __init__.py
└─ language_manager.py
新しく、次のディレクトリとファイルを作ってください。
manager/__init__.py- Pythonに
manager/をパッケージと認識させるために必要なファイルです。
- Pythonに
language_manager.py
language_manager を使った言語管理のモジュール化と設計改善
「言語の選択と管理」を専門に行うモジュールを作ります。
# manager/language_manager.py
def get_language_module(lang_code):
'''言語コードに応じてモジュールを返す'''
if lang_code == 'en':
from message import language_en
return language_en
elif lang_code == 'jp':
from message import language_jp
return language_jp
else:
print('対応していない言語コードです。日本語を使用します。')
from message import language_jp
return language_jp
language_managerモジュールには、言語モジュールを返すget_language_module()関数があります。この関数は、言語コードに対応したモジュールを動的に読み込み、そのモジュールをreturnで返します。
fortune.pyは次のように書き換えます。
# fortune.py
import random
import manager.language_manager as lang_manager
def main():
# 言語を選択
lang_code = input('言語を選んでください (jp/en): ')
# 言語コードに対応したモジュールが返される
lang_module = lang_manager.get_language_module(lang_code)
messages = lang_module.MESSAGES
fortunes = lang_module.FORTUNES
# クッキーを開きますか?
print(f"=== {messages['app_title']} ===")
answer = input(messages['prompt_open'])
if answer == 'y':
print(f"{messages['fortune_prefix']} {random.choice(fortunes)}")
else:
print(messages['goodbye'])
if __name__ == '__main__':
main()
fortune.pyの修正点は次の通りです。
- 【4行目】
import manager.language_manager as lang_managerlanguage_managerモジュールを読み込みます。
- 【11行目】
lang_module = lang_manager.get_language_module(lang_code)get_language_module()関数から言語コードに対応したモジュールを受け取ります。
- 【12, 13行目】言語データモジュールの、変数
lang_module.MESSAGESとlang_module.FORTUNESを、変数messagesとfortunesに代入し直しています。- 改めて別の変数へ代入しなくても、
print出力部分をlang_module.MESSAGES['app_title']のように書いても問題ありません。しかし、これだと可読性が低くなると判断したため、敢えて変数messages,fortunesに代入し直しています。短い記述(ローカル変数への代入)は、コードが簡潔になり、わずかでも可読性が上がるのであれば検討する価値があります。
- 改めて別の変数へ代入しなくても、
- 【25, 26行目】今回から、
fortune.pyのmain()を、if __name__ == '__main__':のブロックの中に入れます。- こうすることで、他のモジュールから
fortuneがimportされた時に、main()が実行されるのを防ぐことができます。
- こうすることで、他のモジュールから
Python多言語対応アプリ:モジュール化のメリットまとめ
- 新しい言語を追加しても修正箇所は
language_manager.pyのみで済むようになった。 fortune.pyはアプリの処理ロジックに専念できる。get_language_module()が単体でテスト可能になった。- これまで、
fortune.pyのmain()関数を実行しなければ、言語データ(モジュール)が正しく読み込めているかどうかを確認することができませんでしたが、これからはlanguage_manager.pyを単体でテストすることができます。
- これまで、
# manager/language_manager.py
def get_language_module(lang_code):
'''言語コードに応じてモジュールを返す'''
if lang_code == 'en':
from message import language_en
return language_en
elif lang_code == 'jp':
from message import language_jp
return language_jp
else:
print('対応していない言語コードです。日本語を使用します。')
from message import language_jp
return language_jp
if __name__ == '__main__':
# テスト用の一時的なsys.pathの追加
import sys
sys.path.append(f'{__file__}/../../')
from pprint import pprint
lang_module = get_language_module('jp')
pprint(lang_module.MESSAGES)
pprint(lang_module.FORTUNES)
テスト用のコード
- 各モジュール単体でテストしたい場合は、
if __name__ == '__main__':ブロック内にテスト用コードを記述することで安全に開発を行うことができます。 sys.pathは、Pythonが「import するときに、どこを探すか」を記録した探索パスのlistです。上記コードの6行目, 9行目のfrom message import language_xxは、fortune_cookie/がルートディレクトリであることを前提にモジュールまでの経路を示しています。しかし、manager/language_manager.pyを直接実行すると、manager/をルートディレクトリとして認識してしまい、importが失敗します。そのため、import時にエラーにならないように、正しいルートディレクトリを設定するための処理を加えています。__file__は特殊変数で、実行中のファイルのパスが格納されています。language_manager.pyまでのパスを基準に、/../../の表記を付け加えることで、現在地から相対的に2つ上の階層(fortune_cookie/)を指定しています。
(相対パスについては専用のレクチャーで学習します。)
相対パス:
f'{__file__}/../../'
# "__file__"を実際のパスに置き換えてみた例
C:\Users\xxx\Documents\python101\fortune_cookie\manager\language_manager.py/../../
\でもスラッシュ/でも、通常は正しく処理されます。(ただし、クロスプラットフォームで確実に動作させるには、osモジュールを使ってパスを操作することが推奨されます。)絶対パス:
C:\Users\xxx\Documents\python101\fortune_cookie






