この記事で切り分けられること
python のスクリプトが毎回同じ箇所で UnboundLocalError を出して落ちる場合に、どこを見れば原因が分かるかをまとめる。特に「代入しているはずの変数なのに未代入エラーになる」ケースを扱う。対象は 2026-11-01 にクラウド側で見つかった run_owner_todo.py の list コマンドが落ちる不具合。
症状
以下のコマンドを実行すると、毎回同じエラーで落ちる。
py -3 scripts/run_owner_todo.py list
UnboundLocalError: cannot access local variable 'rows'
台帳側にはデータがあるのに、コマンドで一覧を見ようとすると1件も表示されない。コックピットの「今日」②にあたる本人作業一覧が、コマンド経由では常に空扱いになっていた。
原因
main() の中に list 分岐と done 分岐があり、done 分岐で次のようなコードになっていた。
def main():
args = parse_args()
if args.command == "list":
for r in rows():
print(r)
if args.command == "done":
rows = requests.get("<あなたのAPIのURL>").json()
mark_done(rows, args.id)
ここでのポイントは、rows という名前がモジュール関数 rows() と同じ名前で、done 分岐の中でローカル変数として代入されていたこと。
Python はコンパイル時に関数全体を見て、その関数内のどこかに代入文があれば、その名前を 関数全体のローカル変数として扱う。これは実行時にどの分岐を通るかとは関係ない。つまり、done 分岐にしか出てこない rows = ... という代入があるだけで、main() 関数全体において rows はローカル変数扱いになる。
その結果、list 分岐の for r in rows(): は、モジュール関数の rows() を呼んでいるつもりでも、Python 側は「まだ代入されていないローカル変数 rows を読もうとしている」と解釈し、UnboundLocalError になる。list コマンドを選んでも done 分岐を一度も実行しなくても、このエラーは起きる。
直し方
done 分岐のローカル変数名を、モジュール関数 rows() と被らない名前に変更する。
def main():
args = parse_args()
if args.command == "list":
for r in rows():
print(r)
if args.command == "done":
open_rows = requests.get("<あなたのAPIのURL>").json()
mark_done(open_rows, args.id)
この変更だけで、list 分岐はモジュール関数 rows() を正しく呼べるようになり、エラーが消えた。
py -3 scripts/run_owner_todo.py list
台帳にある本人作業の一覧が、コマンドでも表示されるようになった。
再発防止
- 関数の外で定義した名前(モジュール関数・モジュール変数など)と同じ名前を、同じ関数内の別の分岐でローカル変数として使わない。
- Python のスコープは「どの分岐を通ったか」ではなく「その関数の中に代入文があるかどうか」で決まる。片方の分岐にしか見えないつもりの代入でも、もう片方の分岐にある同名の呼び出しを壊す。
- 同じ関数内で、呼び出したい関数名とローカル変数名が一致していないか、代入箇所をすべて洗い出して確認する。
それでも駄目なときに次に見る場所
- エラーメッセージに出てくる変数名で、同じ関数内を
grepし、代入文(=の左辺)がないか確認する。 - その変数名がモジュール関数やモジュール変数と同じ名前になっていないか確認する。
- 分岐が複数ある関数では、各分岐でどの名前に代入しているかを一覧にして、重複がないか突き合わせる。
- 修正後は、エラーが出ていた分岐だけでなく、他の分岐も実際に実行して結果を確認する。

Claude Codeとは何か、実際に投げた依頼文と直し方から説明する
Notion API連携の最初の設定をインテグレーション作成から実際に動かす手順
CSVの文字化けと列ずれが止まる場所をPythonのコードで具体的に潰す手順