15

argparseは、グループまたはパーサーを独自の名前空間に解析するための組み込み機能を提供しますか? どこかで選択肢が欠けているような気がします。

編集: この例は、私の目標を達成するためにパーサーを構造化するために私がすべきことではないかもしれませんが、これまでのところ私が解決したことです。私の具体的な目標は、名前空間フィールドに解析されるオプションのグループをサブパーサーに提供できるようにすることです。私が親と一緒に考えたのは、これと同じ目的のために共通のオプションを使用することでした。

例:

import argparse

# Main parser
main_parser = argparse.ArgumentParser()
main_parser.add_argument("-common")

# filter parser
filter_parser = argparse.ArgumentParser(add_help=False)
filter_parser.add_argument("-filter1")
filter_parser.add_argument("-filter2")

# sub commands
subparsers = main_parser.add_subparsers(help='sub-command help')

parser_a = subparsers.add_parser('command_a', help="command_a help", parents=[filter_parser])
parser_a.add_argument("-foo")
parser_a.add_argument("-bar")

parser_b = subparsers.add_parser('command_b', help="command_b help", parents=[filter_parser])
parser_b.add_argument("-biz")
parser_b.add_argument("-baz")

# parse
namespace = main_parser.parse_args()
print namespace

これは明らかに私が得るものです:

$ python test.py command_a -foo bar -filter1 val
Namespace(bar=None, common=None, filter1='val', filter2=None, foo='bar')

しかし、これは私が本当に求めているものです:

Namespace(bar=None, common=None, foo='bar', 
          filter=Namespace(filter1='val', filter2=None))

さらに、すでに名前空間に解析されているオプションのさらに多くのグループ:

Namespace(common=None, 
          foo='bar', bar=None,  
          filter=Namespace(filter1='val', filter2=None),
          anotherGroup=Namespace(bazers='val'),
          anotherGroup2=Namespace(fooers='val'),
          )

ここで関連する質問を見つけましたが、カスタム解析が含まれており、本当に特定の状況のみをカバーしているようです。

特定のグループを名前空間フィールドに解析するよう argparse に指示するオプションはどこかにありますか?

4

7 に答える 7

16

選択した引数を独自の に入れることに重点が置かれてnamespaceおり、サブパーサー (および親) の使用が問題に付随している場合は、このカスタム アクションがうまくいく可能性があります。

class GroupedAction(argparse.Action):    
    def __call__(self, parser, namespace, values, option_string=None):
        group,dest = self.dest.split('.',2)
        groupspace = getattr(namespace, group, argparse.Namespace())
        setattr(groupspace, dest, values)
        setattr(namespace, group, groupspace)

名前の指定にはさまざまな方法がありますgroup。アクションを定義するときに引数として渡すことができます。パラメータとして追加できます。ここでは、 から解析することを選択しましたdest(したがってnamespace.filter.filter1、 の値を取得できますfilter.filter1

# Main parser
main_parser = argparse.ArgumentParser()
main_parser.add_argument("-common")

filter_parser = argparse.ArgumentParser(add_help=False)
filter_parser.add_argument("--filter1", action=GroupedAction, dest='filter.filter1', default=argparse.SUPPRESS)
filter_parser.add_argument("--filter2", action=GroupedAction, dest='filter.filter2', default=argparse.SUPPRESS)

subparsers = main_parser.add_subparsers(help='sub-command help')

parser_a = subparsers.add_parser('command_a', help="command_a help", parents=[filter_parser])
parser_a.add_argument("--foo")
parser_a.add_argument("--bar")
parser_a.add_argument("--bazers", action=GroupedAction, dest='anotherGroup.bazers', default=argparse.SUPPRESS)
...
namespace = main_parser.parse_args()
print namespace

メイン名前空間にエントリが表示されないよう default=argparse.SUPPRESSに追加する必要がありました。bazers=None

結果:

>>> python PROG command_a --foo bar --filter1 val --bazers val
Namespace(anotherGroup=Namespace(bazers='val'), 
    bar=None, common=None, 
    filter=Namespace(filter1='val'), 
    foo='bar')

ネストされた名前空間にデフォルトのエントリが必要な場合は、事前に名前空間を定義できます。

filter_namespace = argparse.Namespace(filter1=None, filter2=None)
namespace = argparse.Namespace(filter=filter_namespace)
namespace = main_parser.parse_args(namespace=namespace)

以下を除いて、以前と同じ結果になります。

filter=Namespace(filter1='val', filter2=None)
于 2013-09-07T20:12:29.470 に答える
9

あなたが何を求めているのか完全にはわかりませんが、あなたが望むのは、引数グループまたはサブコマンドがその引数をサブ名前空間に入れることだと思います。

私の知る限り、これをargparseすぐに行うことはできません。しかし、結果を後処理することは、実際には難しいことではありません。(サブクラス化することでさらに簡単になるArgumentParserと思いますが、それをしたくないと明示的に言ったので、試しませんでした。)

parser = argparse.ArgumentParser()
parser.add_argument('--foo')
breakfast = parser.add_argument_group('breakfast')
breakfast.add_argument('--spam')
breakfast.add_argument('--eggs')
args = parser.parse_args()

現在、breakfastオプションのすべての宛先のリストは次のとおりです。

[action.dest for action in breakfast._group_actions]

のキーと値のペアargsは次のとおりです。

args._get_kwargs()

したがって、一致するものを移動するだけです。辞書を構築して名前空間を作成すると、少し簡単になります。

breakfast_options = [action.dest for action in breakfast._group_actions]
top_names = {name: value for (name, value) in args._get_kwargs()
             if name not in breakfast_options}
breakfast_names = {name: value for (name, value) in args._get_kwargs()
                   if name in breakfast_options}
top_names['breakfast'] = argparse.Namespace(**breakfast_names)
top_namespace = argparse.Namespace(**top_names)

以上です; top_namespace次のようになります。

Namespace(breakfast=Namespace(eggs=None, spam='7'), foo='bar')

もちろん、この場合、1 つの静的グループがあります。より一般的なソリューションが必要な場合はどうしますか? 簡単。parser._action_groupsはすべてのグループのリストですが、最初の 2 つはグローバルな位置グループとキーワード グループです。したがって、 を反復してparser._action_groups[2:]、上記で行ったのと同じことをそれぞれに対して実行しますbreakfast


グループの代わりにサブコマンドはどうですか? 似ていますが、細部が異なります。それぞれのオブジェクトを保持しているsubparser場合、それはまったく別のものArgumentParserです。そうでない場合でも、subparsersオブジェクトを保持した場合、それは特別なタイプの ですActionchoicesこれは、キーがサブパーサー名であり、値がサブパーサー自体である dict です。どちらも保持していない場合は… から始めparser._subparsersて、そこから理解してください。

いずれにせよ、移動したい名前の見つけ方と移動先がわかれば、あとはグループと同じです。


グローバルな引数やグループ、およびサブパーサー固有の引数やグループに加えて、複数のサブパーサーによって共有されるいくつかのグループがある場合…各サブパーサーが同じものへの参照で終わるため、概念的には注意が必要です。すべてのグループに移動することはできません。しかし幸いなことに、1 つのサブパーサーしか扱っていない (またはまったくいない) ため、他のサブパーサーを無視して、共有グループを選択したサブパーサーの下に移動することができます (また、選択したサブパーサーに存在しないグループを移動することもできます)。を一番上に置くか、破棄するか、任意に 1 つのサブパーサーを選択します)。

于 2013-09-07T00:51:52.383 に答える
1

このスクリプト__call__では、argparse._SubParsersAction のメソッドを変更しました。namespaceサブパーサーに on を渡す代わりに、新しいものを渡します。次に、それを main に追加しnamespaceます。の 3 行だけを変更し__call__ます。

import argparse

def mycall(self, parser, namespace, values, option_string=None):
    parser_name = values[0]
    arg_strings = values[1:]

    # set the parser name if requested
    if self.dest is not argparse.SUPPRESS:
        setattr(namespace, self.dest, parser_name)

    # select the parser
    try:
        parser = self._name_parser_map[parser_name]
    except KeyError:
        args = {'parser_name': parser_name,
                'choices': ', '.join(self._name_parser_map)}
        msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args
        raise argparse.ArgumentError(self, msg)

    # CHANGES
    # parse all the remaining options into a new namespace
    # store any unrecognized options on the main namespace, so that the top
    # level parser can decide what to do with them
    newspace = argparse.Namespace()
    newspace, arg_strings = parser.parse_known_args(arg_strings, newspace)
    setattr(namespace, 'subspace', newspace) # is there a better 'dest'?

    if arg_strings:
        vars(namespace).setdefault(argparse._UNRECOGNIZED_ARGS_ATTR, [])
        getattr(namespace, argparse._UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)

argparse._SubParsersAction.__call__ = mycall

# Main parser
main_parser = argparse.ArgumentParser()
main_parser.add_argument("--common")

# sub commands
subparsers = main_parser.add_subparsers(dest='command')

parser_a = subparsers.add_parser('command_a')
parser_a.add_argument("--foo")
parser_a.add_argument("--bar")

parser_b = subparsers.add_parser('command_b')
parser_b.add_argument("--biz")
parser_b.add_argument("--baz")

# parse
input = 'command_a --foo bar --bar val --filter extra'.split()
namespace = main_parser.parse_known_args(input)
print namespace

input = '--common test command_b --biz bar --baz val'.split()
namespace = main_parser.parse_args(input)
print namespace

これにより、次が生成されます。

(Namespace(command='command_a', common=None, 
    subspace=Namespace(bar='val', foo='bar')), 
['--filter', 'extra'])

Namespace(command='command_b', common='test', 
    subspace=Namespace(baz='val', biz='bar'))

parse_known_args余分な文字列がメインのパーサーに返される方法をテストしていました。

parentsこの名前空間の変更には何も追加されないため、削除しました。これは、いくつかのサブパーサーが使用する一連の引数を定義する便利な方法です。 は、 を介して追加された引数と、直接追加さargparseれた引数の記録を保持しません。parentsグループ化ツールではありません

argument_groupsどちらもあまり役に立ちません。これらはヘルプ フォーマッタで使用されますが、 では使用されませんparse_args

_SubParsersAction( を再割り当てする代わりに)サブクラス化することもでき__call__ますが、main_parse.register.

于 2013-09-07T04:18:28.903 に答える
1

abarnert の回答から始めて、同様のオプション名を持つ複数の構成グループを処理する次の MWE++ ;-) をまとめました。

#!/usr/bin/env python2
import argparse, re

cmdl_skel = {
    'description'       : 'An example of multi-level argparse usage.',
    'opts'              : {
        '--foo' : {
            'type'    : int,
            'default' : 0,
            'help'    : 'foo help main',
        },
        '--bar' : {
            'type'    : str,
            'default' : 'quux',
            'help'    : 'bar help main',
        },
    },
    # Assume your program uses sub-programs with their options. Argparse will
    # first digest *all* defs, so opts with the same name across groups are
    # forbidden. The trick is to use the module name (=> group.title) as
    # pseudo namespace which is stripped off at group parsing
    'groups' : [
        {   'module'        : 'mod1',
            'description'   : 'mod1 description',
            'opts'          : {
                '--mod1-foo, --mod1.foo'  : {
                    'type'    : int,
                    'default' : 0,
                    'help'    : 'foo help for mod1'
                },
            },
        },
        {   'module'        : 'mod2',
            'description'   : 'mod2 description',
            'opts'          : {
                '--mod2-foo, --mod2.foo'  : {
                    'type'    : int,
                    'default' : 1,
                    'help'    : 'foo help for mod2'
                },
            },
        },
    ],
    'args'              : {
        'arg1'  : {
            'type'    : str,
            'help'    : 'arg1 help',
        },
        'arg2'  : {
            'type'    : str,
            'help'    : 'arg2 help',
        },
    }
}


def parse_args ():
    def _parse_group (parser, opt, **optd):
        # digest variants
        optv = re.split('\s*,\s*', opt)
        # this may rise exceptions...
        parser.add_argument(*optv, **optd)

    errors = {}
    parser = argparse.ArgumentParser(description=cmdl_skel['description'],
                formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    # it'd be nice to loop in a single run over zipped lists, but they have
    # different lenghts...
    for opt in cmdl_skel['opts'].keys():
        _parse_group(parser, opt, **cmdl_skel['opts'][opt])

    for arg in cmdl_skel['args'].keys():
        _parse_group(parser, arg, **cmdl_skel['args'][arg])

    for grp in cmdl_skel['groups']:
        group = parser.add_argument_group(grp['module'], grp['description'])
        for mopt in grp['opts'].keys():
            _parse_group(group, mopt, **grp['opts'][mopt])

    args = parser.parse_args()

    all_group_opts = []
    all_group_names = {}
    for group in parser._action_groups[2:]:
        gtitle = group.title
        group_opts = [action.dest for action in group._group_actions]
        all_group_opts += group_opts
        group_names = {
            # remove the leading pseudo-namespace
            re.sub("^%s_" % gtitle, '', name) : value
                for (name, value) in args._get_kwargs()
                    if name in group_opts
        }
        # build group namespace
        all_group_names[gtitle] = argparse.Namespace(**group_names)

    # rebuild top namespace
    top_names = {
        name: value for (name, value) in args._get_kwargs()
            if name not in all_group_opts
    }
    top_names.update(**all_group_names)
    top_namespace = argparse.Namespace(**top_names)

    return top_namespace


def main():
    args = parse_args()

    print(str(args))
    print(args.bar)
    print(args.mod1.foo)


if __name__ == '__main__':
    main()

次に、次のように呼び出すことができます(ニーモニック:--mod1-...「mod1」などのオプションです):

$ ./argparse_example.py one two --bar=three --mod1-foo=11231 --mod2.foo=46546
Namespace(arg1='one', arg2='two', bar='three', foo=0, mod1=Namespace(foo=11231), mod2=Namespace(foo=46546))
three
11231
于 2014-05-14T15:08:30.977 に答える
-1

PyPiのargpext モジュールをチェックしてください。役立つかもしれません!

于 2013-10-14T01:23:01.537 に答える