本文へスキップ

ZPL-0484 Python開発 実測 23/25

Pythonの既存コードをimportを壊さずモジュール分割するPrompt

モジュール分割を「考え始める」状態から「次の行動が決まる」状態へ。

現ツリー・import・起動方法から移動順を決め、公開APIとテストを守る分割案を作ります。

  • SAVE VALUE 4
  • 中級
  • 5〜20分
  • ChatGPT / Claude / Gemini

このPromptでできること

  • ファイルツリー、主要コード、公開import、実行コマンドを入力すると、責務別の移動対応表、src/flat判断、循環回避、段階的な回帰確認を作ります。

必要なもの(入力項目)

  • 現行ファイルツリーと大きいファイルのコード
  • 他モジュールからのimport・公開関数/クラス
  • 実行コマンド・entrypoint
  • 共有状態とimport時副作用
  • テスト配置と実行方法
  • 配布/インストール方式(src/flat)
  • Pythonバージョン
  • 変更したい責務と既知の循環

解決できる悩み

  • モジュール分割で何を決めるべきか分からない
  • 一般論しか出ず、自分のケースに使えない
  • 重要な前提やリスクを見落とす
  • 結果を実行可能な形へ落とし込めない

こんな人向け

  • Pythonプログラマーの初心者
  • 実務を時短したい担当者
  • AIの出力を判断材料として使いたい人

使うタイミング

モジュール分割に着手する直前、既存案を見直すとき、または公開・実行前に抜け漏れを確認するとき。

プロンプト本文

{{ }} の部分を自分の情報に置き換えてから使ってください。

PROMPT
既存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

ZEN PROMPT LIBRARY

やりたいことから、
次のPromptを探す。