1. ホーム
  2. python

[解決済み] Argparse: 引数の数が可変の場合の処理方法 (nargs='*')

2023-07-19 11:44:27

質問

私は nargs='*' は可変数の引数を処理するのに十分だと思っていました。どうやらそうではないようで、このエラーの原因がわかりません。

コードです。

p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')

p.parse_args('1 2 --spam 8 8 9'.split())

結果としての名前空間は Namespace(pos='1', foo='2', spam='8', vars=['8', '9']) . その代わりに、argparseはこのエラーを出します。

usage: prog.py [-h] [--spam SPAM] pos foo [vars [vars ...]]
error: unrecognized arguments: 9 8

基本的に、argparseはこれらの追加引数をどこに置けばいいのかわかりません...。なぜでしょうか?

どうすれば解決するのでしょうか?

該当するPythonのバグは 課題15112 .

argparse: nargs='*' 位置引数は、オプションと他の位置引数が前にある場合、項目を受け付けません。

argparseが解析するとき ['1', '2', '--spam', '8', '8', '9'] にマッチしようとします。 ['1','2'] をできるだけ多くの位置引数でマッチさせようとします。 引数を指定すると、パターンマッチの文字列は AAA* : にはそれぞれ1つの引数 posfoo の引数はゼロ、そして vars (ただし * はZERO_OR_MOREを意味します)。

['--spam','8'] は、あなたの --spam 引数によって処理されます。 このため vars はすでに [] に設定されているので ['8','9'] .

へのプログラミングの変更は argparse0 の引数文字列はパターンを満たしているが、まだ optionals はパースされる。 を処理するのを延期する。 * 引数の処理を延期します。

これを回避するには、まず入力をパースして parse_known_args で、次に remainder を別の呼び出しで処理し parse_args .

位置詞の中にオプション詞を自由に入れられるようにするために 問題 14191 を使うことを提案します。 parse_known_args のみで optionals があり、その後に parse_args が続きます。 その parse_intermixed_args 関数に実装することができます。 ArgumentParser のサブクラスで実装することができます。 argparse.py のコード自体を変更することなく


サブパーサーを処理する方法を紹介します。 私は parse_known_intermixed_args 関数を、体裁を整えるために単純化し、それを parse_known_args 関数にしました。 再帰を避けるために余分なステップを踏まなければなりませんでした。

最後に私は _parser_class を変更し、各サブパーサーはこの代替の parse_known_args . 別の方法として、サブクラス _SubParsersAction をサブクラス化し、場合によってはその __call__ .

from argparse import ArgumentParser

def parse_known_intermixed_args(self, args=None, namespace=None):
    # self - argparse parser
    # simplified from http://bugs.python.org/file30204/test_intermixed.py
    parsefn = super(SubParser, self).parse_known_args # avoid recursion

    positionals = self._get_positional_actions()
    for action in positionals:
        # deactivate positionals
        action.save_nargs = action.nargs
        action.nargs = 0

    namespace, remaining_args = parsefn(args, namespace)
    for action in positionals:
        # remove the empty positional values from namespace
        if hasattr(namespace, action.dest):
            delattr(namespace, action.dest)
    for action in positionals:
        action.nargs = action.save_nargs
    # parse positionals
    namespace, extras = parsefn(remaining_args, namespace)
    return namespace, extras

class SubParser(ArgumentParser):
    parse_known_args = parse_known_intermixed_args

parser = ArgumentParser()
parser.add_argument('foo')
sp = parser.add_subparsers(dest='cmd')
sp._parser_class = SubParser # use different parser class for subparsers
spp1 = sp.add_parser('cmd1')
spp1.add_argument('-x')
spp1.add_argument('bar')
spp1.add_argument('vars',nargs='*')

print parser.parse_args('foo cmd1 bar -x one 8 9'.split())
# Namespace(bar='bar', cmd='cmd1', foo='foo', vars=['8', '9'], x='one')