web-dev-qa-db-ja.com

デフォルトのサブコマンド、またはargparseでサブコマンドを処理しない

デフォルトの サブコマンド を設定したり、 argparse を使用してサブコマンドが指定されていない場合を処理するにはどうすればよいですか?

import argparse

a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
a.parse_args()

ここでは、コマンドを選択するか、次に高いレベルのパーサー(この場合は最上位のパーサー)のみに基づいて引数を処理する必要があります。

joiner @ X:〜/ src> python3 default_subcommand.py 
 usage:default_subcommand.py [-h] {hi} ... 
 default_subcommand.py:エラー:引数が少なすぎます
33
Matt Joiner

私は最終的に自分で解決策に出くわしたようです。

コマンドがオプションの場合、これによりコマンドがオプションになります。元のパーサー構成では、可能なステップの範囲を実行できるpackageコマンドがありました。または、何も指定されていない場合はすべてのステップを実行します。これにより、ステップが選択されます。

parser = argparse.ArgumentParser()

command_parser = subparsers.add_parser('command')
command_parser.add_argument('--step', choices=['prepare', 'configure', 'compile', 'stage', 'package'])

...other command parsers

parsed_args = parser.parse_args()

if parsed_args.step is None:
    do all the steps...
7
Matt Joiner

Python 3.2(および2.7)ではエラーが発生しますが、3.3および3.4​​(応答なし)では発生しません。したがって、3.3/3.4では_parsed_args_が空のNamespace

より一般的な解決策は、メソッドset_default_subparser()ruamel.std.argparse パッケージから取得)を追加し、parse_args()の直前にそのメソッドを呼び出すことです。

_import argparse
import sys

def set_default_subparser(self, name, args=None, positional_args=0):
    """default subparser selection. Call after setup, just before parse_args()
    name: is the name of the subparser to call by default
    args: if set is the argument list handed to parse_args()

    , tested with 2.7, 3.2, 3.3, 3.4
    it works with 2.6 assuming argparse is installed
    """
    subparser_found = False
    for arg in sys.argv[1:]:
        if arg in ['-h', '--help']:  # global help if no subparser
            break
    else:
        for x in self._subparsers._actions:
            if not isinstance(x, argparse._SubParsersAction):
                continue
            for sp_name in x._name_parser_map.keys():
                if sp_name in sys.argv[1:]:
                    subparser_found = True
        if not subparser_found:
            # insert default in last position before global positional
            # arguments, this implies no global options are specified after
            # first positional argument
            if args is None:
                sys.argv.insert(len(sys.argv) - positional_args, name)
            else:
                args.insert(len(args) - positional_args, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser

def do_hi():
    print('inside hi')

a = argparse.ArgumentParser()
b = a.add_subparsers()
sp = b.add_parser('hi')
sp.set_defaults(func=do_hi)

a.set_default_subparser('hi')
parsed_args = a.parse_args()

if hasattr(parsed_args, 'func'):
    parsed_args.func()
_

これは、2.6(argparseがPyPIからインストールされている場合)、2.7、3.2、3.3、3.4で機能します。そして、あなたは両方を行うことができます

_python3 default_subcommand.py
_

そして

_python3 default_subcommand.py hi
_

同じ効果で。

既存のサブパーサーの代わりに、デフォルトで新しいサブパーサーを選択できるようにします。

コードの最初のバージョンでは、以前に定義されたサブパーサーの1つをデフォルトとして設定できます。次の変更により、新しいデフォルトのサブパーサーを追加できます。これを使用して、ユーザーがサブパーサーを選択しなかった場合(コードでマークされた別の行)のケースを具体的に処理できます。

_def set_default_subparser(self, name, args=None, positional_args=0):
    """default subparser selection. Call after setup, just before parse_args()
    name: is the name of the subparser to call by default
    args: if set is the argument list handed to parse_args()

    , tested with 2.7, 3.2, 3.3, 3.4
    it works with 2.6 assuming argparse is installed
    """
    subparser_found = False
    existing_default = False # check if default parser previously defined
    for arg in sys.argv[1:]:
        if arg in ['-h', '--help']:  # global help if no subparser
            break
    else:
        for x in self._subparsers._actions:
            if not isinstance(x, argparse._SubParsersAction):
                continue
            for sp_name in x._name_parser_map.keys():
                if sp_name in sys.argv[1:]:
                    subparser_found = True
                if sp_name == name: # check existance of default parser
                    existing_default = True
        if not subparser_found:
            # If the default subparser is not among the existing ones,
            # create a new parser.
            # As this is called just before 'parse_args', the default
            # parser created here will not pollute the help output.

            if not existing_default:
                for x in self._subparsers._actions:
                    if not isinstance(x, argparse._SubParsersAction):
                        continue
                    x.add_parser(name)
                    break # this works OK, but should I check further?

            # insert default in last position before global positional
            # arguments, this implies no global options are specified after
            # first positional argument
            if args is None:
                sys.argv.insert(len(sys.argv) - positional_args, name)
            else:
                args.insert(len(args) - positional_args, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser

a = argparse.ArgumentParser()
b = a.add_subparsers(dest ='cmd')
sp = b.add_parser('hi')
sp2 = b.add_parser('hai')

a.set_default_subparser('hey')
parsed_args = a.parse_args()

print(parsed_args)
_

「デフォルト」オプションはまだヘルプに表示されません:

_python test_parser.py -h
usage: test_parser.py [-h] {hi,hai} ...

positional arguments:
  {hi,hai}

optional arguments:
  -h, --help  show this help message and exit
_

ただし、提供されたサブパーサーの1つを呼び出すことと、引数が指定されていない場合にデフォルトのサブパーサーを呼び出すことを区別して個別に処理できるようになりました。

_$ python test_parser.py hi
Namespace(cmd='hi')
$ python test_parser.py 
Namespace(cmd='hey')
_
18
Anthon

set_default_subparserメソッドを追加するより良い方法は次のとおりです。

class DefaultSubcommandArgParse(argparse.ArgumentParser):
    __default_subparser = None

    def set_default_subparser(self, name):
        self.__default_subparser = name

    def _parse_known_args(self, arg_strings, *args, **kwargs):
        in_args = set(arg_strings)
        d_sp = self.__default_subparser
        if d_sp is not None and not {'-h', '--help'}.intersection(in_args):
            for x in self._subparsers._actions:
                subparser_found = (
                    isinstance(x, argparse._SubParsersAction) and
                    in_args.intersection(x._name_parser_map.keys())
                )
                if subparser_found:
                    break
            else:
                # insert default in first position, this implies no
                # global options without a sub_parsers specified
                arg_strings = [d_sp] + arg_strings
        return super(DefaultSubcommandArgParse, self)._parse_known_args(
            arg_strings, *args, **kwargs
        )
3
Thomas Grainger

たぶんあなたが探しているのはadd_subparsersdest引数です:

警告:Python 3.4で動作しますが、2.7では動作しません

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
parser_hi = subparsers.add_parser('hi')
parser.parse_args([]) # Namespace(cmd=None)

これで、cmdの値を使用できます。

if cmd in [None, 'hi']:
    print('command "hi"')
2

メインパーサーで特定のサブパーサーのデフォルトアクションを複製して、事実上デフォルトにすることができます。

_import argparse
p = argparse.ArgumentParser()
sp = p.add_subparsers()

a = sp.add_parser('a')
a.set_defaults(func=do_a)

b = sp.add_parser('b')
b.set_defaults(func=do_b)

p.set_defaults(func=do_b)
args = p.parse_args()

if args.func:
    args.func()
else:
    parser.print_help()
_

add_subparsers(required=True)では機能しません。そのため、_if args.func_があります。

0
Evgeny

python 2.7では、サブクラスのエラー動作をオーバーライドできます(残念ながら、エラーを区別するためのより良い方法はありません)。

import argparse

class ExceptionArgParser(argparse.ArgumentParser):

    def error(self, message):
        if "invalid choice" in message:
            # throw exception (of your choice) to catch
            raise RuntimeError(message)
        else:
            # restore normal behaviour
            super(ExceptionArgParser, self).error(message)


parser = ExceptionArgParser()
subparsers = parser.add_subparsers(title='Modes', dest='mode')

default_parser = subparsers.add_parser('default')
default_parser.add_argument('a', nargs="+")

other_parser = subparsers.add_parser('other')
other_parser.add_argument('b', nargs="+")

try:
    args = parser.parse_args()
except RuntimeError:
    args = default_parser.parse_args()
    # force the mode into namespace
    setattr(args, 'mode', 'default') 

print args
0
Frank Dumont

何も設定されていないときに使用されるデフォルト値の引数を追加できます。

これを参照してください: http://docs.python.org/dev/library/argparse.html#default

編集:

申し訳ありませんが、私はあなたの質問を少し速く読みました。

Argparseを介してやりたいことを直接行う方法はないと思います。ただし、sys.argvの長さを確認でき、その長さが1(スクリプト名のみ)の場合は、次のようにして、解析用のデフォルトパラメーターを手動で渡すことができます。

import argparse

a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')

if len(sys.argv) == 1:
   a.parse_args(['hi'])
else:
   a.parse_args()

私はそれがあなたが望むことをするべきだと思います、しかし私はこれを箱から出して持っていればいいと思います。

0
Locksfree

私の場合、argvが空のときに、サブコマンド名をparse_args()に明示的に指定するのが最も簡単であることがわかりました。

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')

runParser = subparsers.add_parser('run', help='[DEFAULT ACTION]')

altParser = subparsers.add_parser('alt', help='Alternate command')
altParser.add_argument('alt_val', type=str, help='Value required for alt command.')

# Here's my shortcut: If `argv` only contains the script name,
# manually inject our "default" command.
args = parser.parse_args(['run'] if len(sys.argv) == 1 else None)
print args

実行例:

$ ./test.py 
Namespace()

$ ./test.py alt blah
Namespace(alt_val='blah')

$ ./test.py blah
usage: test.py [-h] {run,alt} ...
test.py: error: invalid choice: 'blah' (choose from 'run', 'alt')
0
beporter