Error [ValueError: argument groups cannot be nested]

ValueError: argument groups cannot be nested の直し方(Python 3.14 の argparse)

FIX SUMMARY verified
Applies when
python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1Creating an argument group from another argument group; deprecated since Python 3.11, Python 3.14 raises ValueError at definition time

Verified: reproduced in python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1, then the ValueError: argument groups cannot be nested signature was gone after the fix (exit 0).

Python を 3.14 に上げたら、コマンドライン引数の定義で落ちます。

ValueError: argument groups cannot be nested

argparse で、引数グループから別の引数グループを作っているコードです。

import argparse

parser = argparse.ArgumentParser()
database = parser.add_argument_group("database")
credentials = database.add_argument_group("credentials")   # ここで ValueError
credentials.add_argument("--user")

parser.parse_args() を呼ぶ前、グループを定義した時点で落ちます。

直し方

両方のグループを ArgumentParser から作ります。

parser = argparse.ArgumentParser()
database = parser.add_argument_group("database")
credentials = parser.add_argument_group("credentials")   # parser から作る
credentials.add_argument("--user")

この平坦化で失うものはありません。 理由は次の節のとおりで、入れ子は 3.13 の時点で見た目にも反映されていなかったからです。

入れ子は 3.13 でも動いていませんでした

「3.14 で禁止された」と聞くと、3.13 までは入れ子が機能していたように思えます。していません。

Python 3.13 で、入れ子にしたグループへ引数を足して --help を表示させた結果です(実測)。

parser = argparse.ArgumentParser(prog="app")
database = parser.add_argument_group("database")
database.add_argument("--host")
credentials = database.add_argument_group("credentials")   # 3.13 では通る
credentials.add_argument("--user")
parser.print_help()
usage: app [-h] [--host HOST] [--user USER]

options:
  -h, --help   show this help message and exit

database:
  --host HOST

credentials の節が、ヘルプに出ていません。 --user は usage 行には現れますが、本体には database の節しかなく、credentials という見出しも --user の説明も表示されていません。

引数のパース自体は動くので、credentials.add_argument("--user") で足した --user は受け付けられます。しかしグループとして分けた意味は、3.13 の時点ですでに失われていました。だから parser の直下に並べても、ヘルプの見え方はむしろ正しくなります(credentials の節が出るようになります)。

そもそも argparse の引数グループは、ヘルプを平らな節に分けるだけで、子グループを階層表示する機能を持ちません。入れ子にした親子関係を --help に反映する方法は無いので、平坦化して失うものはありません。CLI で階層的な構造を見せたいなら、それは argparse の範囲外です(サブコマンド add_subparsers() など、別の仕組みを使います)。

Python 3.11 から、入れ子には DeprecationWarning: Nesting argument groups is deprecated. が出ていました(実測)。3.14 はそれを ValueError にしました。

グループの中に相互排他グループを作る形は、3.14 でも通ります

止まるのは add_argument_group() の入れ子です。引数グループの中に相互排他グループ(mutually exclusive group)を作るのは、3.14 でも正当です(実測)。

parser = argparse.ArgumentParser()
database = parser.add_argument_group("database")
mode = database.add_mutually_exclusive_group()   # 3.14 でも通る
mode.add_argument("--read-only", action="store_true")
mode.add_argument("--read-write", action="store_true")

これはよくある正当なパターンで、禁止されていません。3.14 が ValueError で拒否するのは、次の 3 経路です(実測)。

呼び出し3.14
group.add_mutually_exclusive_group()通る(上の例)
group.add_argument_group()ValueError: argument groups cannot be nested
mutex.add_argument_group()ValueError: argument groups cannot be nested
mutex.add_mutually_exclusive_group()ValueError: mutually exclusive groups cannot be nested

相互排他グループの中に相互排他グループを作る形も落ちます(別のメッセージです)。通るのは「引数グループの中に相互排他グループ」の 1 経路だけです。

予告は既定では見えないことがあります

DeprecationWarning が実際に表示されるかは、どこでグループを定義しているかで変わります。

  • スクリプトを python app.py で直接実行し、その __main__ の中でグループを定義していれば、-W を付けなくても警告が出ます。
  • パーサの構築を別のモジュール(cli.py など)に切り出していると、既定のフィルタでは出ません。python -W always::DeprecationWarning app.py で走らせる必要があります。

CLI ツールはパーサ構築を関数やモジュールに分けていることが多いので、後者になりがちです。3.14 に上げる前に確認したいなら、-W always::DeprecationWarning を付けて一度実行してください。

切り分け

  • parse_args() を呼ぶ前に落ちる → このエラーです。グループの定義時点で送出されます。
  • add_argument_group() を入れ子にしている → これが原因です。両方を parser 直下に移してください。
  • 相互排他グループを使っているのに落ちる → 引数グループの中の相互排他グループ自体は正当です。その相互排他グループからさらにグループadd_argument_group() でも add_mutually_exclusive_group() でも)を作っていないか確認してください。どちらも落ちます。
  • 平坦化したら CLI の階層表示が消えた → argparse の引数グループはもともとヘルプを平らな節に分けるだけで、階層表示の機能はありません。階層が要るならサブコマンド(add_subparsers())など別の仕組みを使ってください。
  • 警告が一度も出なかったのに 3.14 で落ちた → パーサ構築が __main__ の外にあります。-W always::DeprecationWarning を付けると出ます。
  • 他の Python 3.14 のエラーを踏んだ → 同じ版境界の姉妹記事があります。functools.partial がクラス属性で壊れる は、同じく「触っていないコードが 3.14 で落ちる」型です。

検証環境

  • python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1(Python 3.14.6)、ネットワーク不要
  • 再現:parser.add_argument_group() で作ったグループから add_argument_group() を呼び、ValueError: argument groups cannot be nested が出て終了コード 1
  • 修正:両方のグループを parser から作り、同じ Python 3.14.6 で終了コード 0(--user の値を出力)・シグネチャ消滅
  • 3.13 では入れ子が通るが --help に子グループの節が出ないこと、group.add_mutually_exclusive_group() は 3.14 でも通ること、group.add_argument_group()mutex.add_argument_group()argument groups cannot be nestedmutex.add_mutually_exclusive_group()mutually exclusive groups cannot be nested になること、警告の表示が呼び出し元が __main__ かどうかで変わることは、python:3.12-slim(3.12.13)・python:3.13-slim(3.13.14)・3.14.6 の 3 版でそれぞれ実行して確認しました
  • 3.12 と 3.13 の間、3.13 と 3.14 の間のパッチ版は測っていません

検証(machine-verified)

この修正は python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1 のバージョン固定コンテナ内で再現し、修正後に ValueError: argument groups cannot be nested のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/argparse-nested-groups-python-314
● reproduce ValueError: argument groups cannot be nested present ✓
● apply fix exit 0
● re-run ValueError: argument groups cannot be nested gone ✓
PASS verified · python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1 · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。