ZPL-0484 Python開発 実測 23/25
Pythonの既存コードをimportを壊さずモジュール分割するPrompt
モジュール分割を「考え始める」状態から「次の行動が決まる」状態へ。
現ツリー・import・起動方法から移動順を決め、公開APIとテストを守る分割案を作ります。
このPromptでできること
- ファイルツリー、主要コード、公開import、実行コマンドを入力すると、責務別の移動対応表、src/flat判断、循環回避、段階的な回帰確認を作ります。
必要なもの(入力項目)
- 現行ファイルツリーと大きいファイルのコード
- 他モジュールからのimport・公開関数/クラス
- 実行コマンド・entrypoint
- 共有状態とimport時副作用
- テスト配置と実行方法
- 配布/インストール方式(src/flat)
- Pythonバージョン
- 変更したい責務と既知の循環
解決できる悩み
- モジュール分割で何を決めるべきか分からない
- 一般論しか出ず、自分のケースに使えない
- 重要な前提やリスクを見落とす
- 結果を実行可能な形へ落とし込めない
こんな人向け
- Pythonプログラマーの初心者
- 実務を時短したい担当者
- AIの出力を判断材料として使いたい人
使うタイミング
モジュール分割に着手する直前、既存案を見直すとき、または公開・実行前に抜け漏れを確認するとき。
プロンプト本文
{{ }} の部分を自分の情報に置き換えてから使ってください。
既存Pythonコードを安全にmodule/packageへ分割する移行案を作ってください。見栄えのよいディレクトリ構造より、公開importと起動方法を壊さないことを優先します。
現行ツリーと対象ファイルの主要コード:{{現ツリー・コード}}
外部/内部から利用される公開import:{{公開import}}
実行コマンドやentrypoint:{{起動コマンド}}
共有状態とimport時副作用:{{共有状態・副作用}}
現行テストと実行方法:{{既存テスト}}
配布/インストール方式とsrc/flat layout:{{配布とlayout}}
対象Python版:{{Python版}}
分けたい責務、既知の循環import:{{分けたい責務・循環}}
関数・状態・I/Oを変更理由でcluster化し、移動前後の対応表を作る。公開APIの互換を守るため旧importの利用箇所を列挙し、必要なら薄い互換層を検討。src layoutはインストール/テスト時のimport差を理由に選び、無条件に切り替えない。python file.py と python -m package の挙動差、循環、import時実行を確認する。1回で全ファイルを移動せず、各段階で起動・unit・import smoke testを実行する。コードを提示されていないファイルについて正確なdiffを捏造しない。
出力:移行後ツリー(注:案)、移動対応表、公開API/entrypoint維持策、段階的変更順、各段階の確認コマンド候補、戻し方、未確認箇所。
使い方
返ってくるもの
- 移行後ツリー、移動対応表、import/entrypoint維持策、段階テストと戻し方
調整のしかた
「目的・利用者」を変更すると対象が変わり、「性能・セキュリティ制約」を具体化すると提案精度が上がります。
追加で投げると効く指示
- 初心者向けに手順を細かくしてください
- 上位3案を比較表にしてください
- 不足情報だけ質問してください
- リスクの高い順に並べてください
推奨モデル
コーディング能力・高推論モデル推奨
実測の入力と出力
下は実際にAIへ通したときの入力と、そのとき返ってきた出力です(モデル: gpt-5.1-thinking / 2026-09-16)。作例ではありません。
このとき入力した内容を見る
既存Pythonコードを安全にmodule/packageへ分割する移行案を作ってください。見栄えのよいディレクトリ構造より、公開importと起動方法を壊さないことを優先します。 現行ツリーと対象ファイルの主要コード:reporttool/ main.py(約1,200行) config.ini tests/test_main.py main.py の主要部分: - 先頭で config.ini を読み込み、グローバル変数 CONFIG に入れる(import 時に実行) - def load_sales(path): CSV を読み込み dict のリストを返す - def calc_monthly(rows): 月別集計 - def render_html(summary): HTML 文字列を作る - def send_mail(html): SMTP で送信(CONFIG を参照) - if __name__ == "__main__": 引数を読んで上の関数を順に呼ぶ 外部/内部から利用される公開import:社内の別スクリプト(nightly.py)が「from main import load_sales, calc_monthly」で使っている。ほかに利用箇所があるかは未調査。 実行コマンドやentrypoint:cron で「cd /opt/reporttool && python main.py --month 2026-08」を実行。 共有状態とimport時副作用:CONFIG(import 時に config.ini を読む)。send_mail は CONFIG の SMTP 設定を使う。import しただけで config.ini が無いと例外になる。 現行テストと実行方法:tests/test_main.py に calc_monthly のテストが3件だけ。実行は「cd /opt/reporttool && python -m pytest」。 配布/インストール方式とsrc/flat layout:パッケージとして配布していない(pip install していない)。フラットな配置。 対象Python版:Python 3.10 分けたい責務、既知の循環import:読み込み(load)、集計(calc)、表示(render)、送信(mail)、設定(config)に分けたい。循環 import はまだ起きていない。 関数・状態・I/Oを変更理由でcluster化し、移動前後の対応表を作る。公開APIの互換を守るため旧importの利用箇所を列挙し、必要なら薄い互換層を検討。src layoutはインストール/テスト時のimport差を理由に選び、無条件に切り替えない。python file.py と python -m package の挙動差、循環、import時実行を確認する。1回で全ファイルを移動せず、各段階で起動・unit・import smoke testを実行する。コードを提示されていないファイルについて正確なdiffを捏造しない。 出力:移行後ツリー(注:案)、移動対応表、公開API/entrypoint維持策、段階的変更順、各段階の確認コマンド候補、戻し方、未確認箇所。
返ってきた出力を見る
このケースでは、最終形を一気に作るより「main.py を互換ファサードとして最後まで残す」移行が安全です。特に from main import ... と python main.py ... を同時に壊さないことを最優先にします。
1. 移行後ツリー案
src/ にはまだ移しません。現状はインストールされておらず、cd /opt/reporttool を前提に直接実行しているため、src/ 化すると import 条件まで同時に変わるからです。
reporttool/
├── main.py # 旧import + 旧起動方法を守る互換層
├── config.ini # 当面そのまま
│
├── reporttool/ # 新しい実装package
│ ├── __init__.py # 最初は極力空にする
│ ├── __main__.py # 将来: python -m reporttool
│ ├── cli.py # CLI制御
│ ├── config.py # 設定読込
│ ├── load.py # CSV読込
│ ├── calc.py # 集計
│ ├── render.py # HTML生成
│ └── mail.py # SMTP送信
│
└── tests/
├── test_main.py # 既存3テスト。当面残す
├── test_public_api.py # 旧import互換
├── test_calc.py # 後でtest_mainから移せる
├── test_cli.py # CLI smoke
└── test_config.py # 設定読込のテスト
依存方向は基本的に次だけにします。
main.py
↓
reporttool.cli
├── config
├── load
├── calc
├── render
└── mail
load ─┐
calc │
render ├─ 原則として互いをimportしない
mail │
config ─┘
特に mail.py -> cli.py や calc.py -> main.py のような逆向き依存を作らないことが循環 import 防止になります。
2. 移動対応表
現在 移行先 旧 main からの公開 移動理由
load_sales(path) reporttool/load.py 維持 CSV I/Oという単一責務
calc_monthly(rows) reporttool/calc.py 維持 純粋な計算処理として独立性が高い
render_html(summary) reporttool/render.py 維持 表示形式生成を集計から分離
send_mail(html) reporttool/mail.py 旧signatureをwrapperで維持 SMTPという外部I/Oを隔離
CONFIG reporttool/config.py 移行初期は main.CONFIG も維持候補 共有状態とimport副作用を局所化
CLI引数解析 reporttool/cli.py python main.py ... から呼ぶ CLIと業務ロジックを分離
if __name__ == "__main__" main.py に残す+後で __main__.py 維持 cronを壊さない
新規 python -m reporttool reporttool/__main__.py 新しい起動経路 packageとして正しい実行経路を追加
重要なのは、「移動」と「公開名の変更」を同時にしないことです。
たとえば移動後も概念的には main.py から、
Python
実行する
from reporttool.load import load_sales
from reporttool.calc import calc_monthly
from reporttool.render import render_html
のように再公開できます。
したがって既存の
Python
実行する
from main import load_sales, calc_monthly
はそのまま通ります。
3. 公開API維持策
現在判明している公開APIは、
Python
実行する
from main import load_sales, calc_monthly
です。
ただし「ほかに利用箇所があるか未調査」なので、コード移動前に利用者を確定する工程を入れるべきです。
Linux上なら候補として、
Bash
cd /opt
rg 'from main import|import main' .
対象を社内リポジトリに限定できるなら、その範囲で検索します。
さらに、次も探します。
Bash
rg 'main\.CONFIG|main\.send_mail|main\.render_html|main\.load_sales|main\.calc_monthly' .
実行方法についても、
Bash
rg 'python(3)? main\.py|/opt/reporttool/main\.py' .
で cron 以外の呼び出しを探します。
main.py はすぐ削除しない
少なくとも今回の移行中は main.py を正式な互換層として残します。
つまり、
旧利用者
↓
main.py
↓
reporttool/*.py
とします。
移行直後に社内スクリプトまで
Python
実行する
from reporttool.load import load_sales
へ書き換える必要はありません。
先に新実装を安定させ、旧importの利用者が把握できてから切替を検討します。
4. CONFIG は構造変更と副作用除去を分ける
ここが今回もっとも事故が起きやすい部分です。
現在、
import main
↓
config.iniを読む
↓
存在しなければ例外
になっています。
これは改善対象ですが、module分割と同時に直さない方が安全です。
第1段階
まず旧動作を維持します。
概念的には、
main.py import
↓
CONFIGを従来通り生成
↓
load_sales等を新moduleから再公開
です。
この時点では、
Python
実行する
from main import calc_monthly
しただけでも config.ini が必要という変な仕様が残ります。
ただしこれは意図的です。
構造だけ変えて、挙動は変えない。
第2段階
利用箇所を調査して、
Python
実行する
from main import CONFIG
や
Python
実行する
main.CONFIG
への依存がないことを確認したあと、初めて、
import main
ではconfigを読まず、
CLI起動
↓
load_config()
↓
send_mail(..., config)
へ変えます。
理想的には内部のmail処理は、
send_mail(html, config)
のように依存を明示します。
一方、旧APIの
Python
実行する
main.send_mail(html)
を利用するコードが判明した場合は main.py 側だけで、
CONFIG取得
↓
新send_mail(html, CONFIG)
というwrapperにすれば互換性を守れます。
5. config.ini のパス規則も勝手に変えない
現在 cron が、
Bash
cd /opt/reporttool && python main.py --month 2026-08
なので、仮にコードが
Python
実行する
open("config.ini")
相当なら、設定ファイルはカレントディレクトリ基準です。
分割時につい、
__file__ 基準で
/opt/reporttool/config.ini
へ変更したくなりますが、これも別の挙動変更です。
たとえば現在、
Bash
cd /tmp
python /opt/reporttool/main.py ...
では失敗する可能性があります。
これを移行後に突然成功するようにすると、改善ではあっても「構造リファクタリングだけ」ではなくなります。
初期移行では現在の探索規則を維持し、設定探索方法の改善は別commitに分離するのが安全です。
6. 段階的な変更順
Phase 0 — 現行状態を固定する
まだ移動しません。
まず現在の挙動を記録します。
Bash
cd /opt/reporttool
python --version
python -m pytest
python -c "from main import load_sales, calc_monthly; print(callable(load_sales), callable(calc_monthly))"
ここで config.ini を一時的に見つけられなくした場合の挙動も、可能なら確認します。
目的は、
「リファクタ後に何が変わったのか」
を判断できるようにすることです。
SMTP送信を伴うCLI本番実行は、リファクタリング確認だけのためには実行しません。
Phase 1 — calc_monthly だけ移す
最初に最も副作用が少ない calc_monthly を、
reporttool/calc.py
へ移します。
main.py からは再公開。
既存テストはまだ、
Python
実行する
from main import calc_monthly
のままでも構いません。
確認
Bash
python -m pytest
python -c "from main import calc_monthly; print(callable(calc_monthly))"
python -c "from reporttool.calc import calc_monthly; print(callable(calc_monthly))"
ここで失敗したら、calc_monthly だけ戻せます。
Phase 2 — load_sales
次にCSV I/Oを移します。
main.py
↓
reporttool.load.load_sales
確認
Bash
python -m pytest
python -c "from main import load_sales, calc_monthly; print('OK')"
python -c "from reporttool.load import load_sales; print('OK')"
可能なら匿名化CSV fixtureを作って、
入力CSV
↓
旧仕様と同じdict list
になっていることをテスト化します。
Phase 3 — render_html
表示生成を分離します。
この段階でもCLIは main.py のままです。
確認:
Bash
python -m pytest
python -c "from main import render_html; print(callable(render_html))"
HTMLが決定的なら、代表的なsummaryに対する出力比較を追加できます。
Phase 4 — config読込コードを config.py へ抽出
ここではまだimport時実行そのものは消さない方針です。
つまり、
設定を読む実装
だけを config.py に移し、
いつ読むか
はまだ変えません。
これにより、
責務分離
と
import副作用除去
を別問題として扱えます。
確認:
Bash
python -m pytest
python -c "import main; print('main imported')"
加えて CONFIG が既存と同じ値を保持していることを、秘密情報を表示しない方法で確認します。
たとえば必要section/keyの存在だけです。
Phase 5 — mailを分離
SMTP処理を、
reporttool/mail.py
へ移します。
内部実装ではCONFIGをグローバル参照するより、
html
config
を入力として受け取る設計が望ましいです。
ただし旧 main.send_mail(html) が使われている可能性があるため、main.py にwrapperを残します。
ここでは実SMTPへ送らないテストを追加します。
pytestのmonkeypatch/mock等で、
SMTP接続先
From/To
message内容
を確認する方が安全です。
Phase 6 — CLIを cli.py に移す
この段階まで旧、
Bash
python main.py --month 2026-08
を維持します。
構造としては、
main.py
if __name__ == "__main__":
↓
reporttool.cli.main(...)
だけに近づけます。
ただしCLI引数については、
オプション名
必須/任意
default
exit code
stdout
stderr
を変更しません。
確認候補
安全なら、
Bash
python main.py --help
そして、
Bash
python main.py --month 2026-08
は本番SMTPを使わない試験環境またはmock環境で確認します。
--help が現在未実装なら、それを「リファクタ確認のために追加」しない方がよいです。それ自体が機能追加になるためです。
Phase 7 — python -m reporttool を追加
ここで初めて、
Bash
python -m reporttool --month 2026-08
を追加します。
reporttool/__main__.py は cli.main() を呼ぶだけにします。
ただしcronはまだ変更しません。
つまり一定期間、
Bash
python main.py --month 2026-08
と
Bash
python -m reporttool --month 2026-08
を両方使える状態にします。
7. python file.py と python -m package の違い
今回ここは重要です。
現在の、
Bash
python main.py
では、main.py がスクリプトとして実行されます。
一方、
Bash
python -m reporttool
では、Pythonがpackageとして reporttool/__main__.py を実行します。
そのためpackage内部では、
reporttool.cli
reporttool.config
といったpackageとしてのimportが安定します。
逆に、
Bash
python reporttool/cli.py
を正式な起動方法にはしない方がよいです。
package内部のrelative importを使った場合、
Bash
python reporttool/cli.py
と
Bash
python -m reporttool
ではimport解決条件が変わるからです。
正式経路は最終的に、
旧互換:
python main.py
新経路:
python -m reporttool
の2つに限定するのがよいです。
8. import時副作用を消す段階
ここまで安定してから別変更として行います。
目標は、
Bash
python -c "import main"
と
Bash
python -c "import reporttool.calc"
が、config.ini なしでも成功することです。
つまり、
import
≠
アプリケーション起動
にします。
設定を必要とするのは、
CLI実行
SMTP送信
のタイミングだけにします。
ただしAcceptance Condition
副作用除去前に、
main.CONFIGを使っている外部コードがない
ことを確認します。
もし存在した場合は、即削除しません。
9. src/ layoutを今回は採用しない理由
今回、
src/
└── reporttool/
へ移すと、通常は
Bash
pip install -e .
などでpackageをインストールして使う前提になります。
しかし現在は、
pyproject.tomlなし
pip installなし
cronから直接実行
pytestもrepo rootから実行
です。
ここで src/ 化すると、
module分割
+
package化
+
install方式変更
+
pytest import条件変更
+
cron実行環境変更
を一度に行うことになります。
今回の目的と逆です。
したがってまず、
/opt/reporttool/reporttool/
という通常packageをrepo直下に作る方が安全です。
将来、
pyproject.toml
venv
pip install .
console_scripts
まで導入すると決まった段階で、別migrationとして src/ layoutを評価するのが適切です。
10. 各段階で必ず残したい smoke test
少なくとも次の3種類を毎phase実行します。
Bash
# Unit
cd /opt/reporttool
python -m pytest
# 既存公開API
python -c "from main import load_sales, calc_monthly; print('legacy import OK')"
# 新package
python -c "import reporttool; print('package import OK')"
CONFIG 副作用をまだ維持している段階では、
Bash
python -c "import main"
について現在と同じ条件で成功することを確認。
副作用除去後は逆に、
Bash
cd /tmp
python -c "import sys; sys.path.insert(0, '/opt/reporttool'); import main; print('OK')"
のようなテストで、CWDに config.ini がなくてもimportだけなら通ることを確認できます。
11. 公開API専用テストを追加する
既存 test_main.py の3件だけでは、今回一番大事な互換性を守れません。
最低でも tests/test_public_api.py で、
from main import load_sales
from main import calc_monthly
が成功することをテスト対象にします。
調査の結果ほかにも公開利用があるなら、
render_html
send_mail
CONFIG
も互換対象に追加します。
これにより後の開発者が、
「main.py もう要らなそうだから消しました」
とやってCIを通してしまう事故を防げます。
12. 戻し方
一括migrationにせず、最低でも次の単位でcommitを分けます。
1. baseline tests追加
2. calc移動
3. load移動
4. render移動
5. config抽出
6. mail移動
7. cli移動
8. python -m reporttool追加
9. import時CONFIG読込除去
各commitで main.py が互換層として残っていれば、たとえばmail分離で問題が起きても、
mail処理だけmain.pyへ戻す
ことができます。
Git運用なら原則、
Bash
git revert <該当commit>
で1責務だけ戻せる粒度にします。
全module分割を1commitにしないことが、実質的なrollback戦略になります。
cronについてはmigration中、
Bash
cd /opt/reporttool && python main.py --month 2026-08
を変更しないのが最も安全です。
新 python -m reporttool が十分確認されたあと、cron変更をさらに別commit・別リリースにします。
13. 未確認箇所
今回の情報だけでは、以下は断定しません。
未確認 移行前に確認する理由
from main import ... の全利用者 公開API範囲を確定するため
import main 後の main.CONFIG 利用 CONFIG副作用を消せるか決めるため
send_mail / render_html の外部利用 wrapperを残す対象になる
config.ini の実際の読込コード CWD基準かファイル基準か確定できない
configのsection/key構造 正確なconfig module設計に必要
CLI引数解析の実装 正確なentrypoint diffは現時点で作れない
CLIのexit code/stdout/stderr cron監視への影響確認
CSVのencoding・newline等 load_sales 移動時の隠れたI/O仕様
HTML生成でCONFIGを参照していないか renderの独立性確認
SMTPの例外処理 mail分離時に例外境界を変えないため
main.py 周辺の相対パス利用 python -m 移行時に差が出る可能性
cronの環境変数/PATH python 実体が対話環境と違う可能性
systemd・shell script等からの別起動 cron以外のentrypoint保護
Python package名 reporttool の既存衝突 新package import前に確認
将来pip配布する予定 src/ layout導入時期の判断材料
したがって、提示されていない main.py 全1,200行について「このdiffで正しい」といった変更案は作らず、symbol単位で抽出→互換層から再公開→テスト→次へを繰り返すのが適切です。
このケースの移行原則
最終的には、
旧:
main.py = 実装1200行 + CONFIG + CLI
↓
移行中:
main.py = 互換API + 旧entrypoint
reporttool/* = 実装
↓
安定後:
main.py = 非常に薄いlegacy facade
reporttool/__main__.py = 新entrypoint
reporttool/* = 実装
とするのが安全です。
特に最初の成功条件は「きれいなpackageになった」ではなく、nightly.py が無変更で動く、cronコマンドも無変更で動く、既存pytestも通るの3点です。
この出力になっていれば成功
- 分割境界に理由
- 公開importと起動を維持
- 循環なし
- 小単位で回帰検証できる
使うときの注意
実行前にGit差分またはバックアップを確認し、テスト環境で検証してください。
現ツリー/利用箇所不明なら具体的なdiffや完成コードを作らず、必要な調査を示す。
参考にした一次情報: Python Docs / Stack Overflow