web-dev-qa-db-ja.com

Argparse:可変個の引数を処理する方法(nargs = '*')

_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はこれらの追加の引数をどこに置くべきかを知りません...なぜですか?

48
rubik

関連するPythonバグは Issue 15112 です。

_argparse: nargs='*'_定位置引数は、オプションと別の定位置が先行する場合、項目を受け入れません。

Argparseが_['1', '2', '--spam', '8', '8', '9']_を解析するとき、最初に_['1','2']_をできるだけ多くの位置引数と一致させようとします。引数のパターンマッチング文字列は_AAA*_:posおよびfooにそれぞれ1つの引数、およびvarsにゼロの引数(_*_はZERO_OR_MOREを意味します)。

_['--spam','8']_は、_--spam_引数によって処理されます。 varsはすでに_[]_に設定されているため、_['8','9']_を処理するものは何もありません。

argparseへのプログラミング変更は、_0_引数文字列がパターンを満たしている場合をチェックしますが、解析されるoptionalsがまだあります。次に、その_*_引数の処理を延期します。

最初に _parse_known_args_ で入力を解析し、次に別の呼び出しでremainderを処理することで、これを回避できる場合があります _parse_args_ .

issue 14191 で位置間のオプションを散在させる完全な自由を得るために、optionalsだけで_parse_known_args_を使用することを提案します。位置のみを知っている_parse_args_によって。そこで投稿した_parse_intermixed_args_関数は、_argparse.py_コード自体を変更せずに、ArgumentParserサブクラスに実装できます。


サブパーサーの処理方法は次のとおりです。私は _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')
_
32
hpaulj

簡単な解決策:posおよびfooを指定する前に、--spamフラグを指定します。

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('--spam 8 1 2 8 9'.split())

変数引数を指定した後に--spamフラグを置いた場合も同じことが言えます。

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 8 9 --spam 8'.split())

編集:それが価値があるものについては、*+に変更するとエラーも修正されるようです。

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())
11
caleb531