21

Python 2.7のargparseパッケージを使用して、コマンドラインツールのオプション解析ロジックを記述しています。ツールは、次のいずれかの引数を受け入れる必要があります。

「ON」:機能をONにします。
「OFF」:機能をオフにします。
[引数は提供されていません]:関数の現在の状態をエコーし​​ます。

argparseのドキュメントを見ると、これら3つの状態は相互に排他的であり、異なる概念的なアクティビティを表すため、2つ(場合によっては3つ)のサブコマンドを定義する必要があると思いました。これは私の現在のコードの試みです:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=print_state) # I think this line is wrong.

parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=set_state, newstate='ON')

parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=set_state, newstate='OFF')

args = parser.parse_args()

if(args.func == set_state):
    set_state(args.newstate)
elif(args.func == print_state):
    print_state()
else:
    args.func() # Catchall in case I add more functions later

func=print_state0個の引数を指定するとメインパーサーが設定され、1個の引数を指定するとメインパーサーが適切なサブコマンドのデフォルトを使用してを呼び出すという印象を受けましたfunc=set_state。代わりに、引数が0の場合に次のエラーが発生します。

usage: cvsSecure.py [-h] {ON,OFF} ...
cvsSecure.py: error: too few arguments

また、「OFF」または「ON」をprint_state指定すると、の代わりに呼び出されset_stateます。parser.set_defaults行をコメントアウトすると、set_state正しく呼び出されます。

私はジャーニーマンレベルのプログラマーですが、Pythonの初心者です。これを機能させる方法について何か提案はありますか?

編集:私がサブコ​​マンドを見ていましたもう1つの理由は、将来的に検討している潜在的な4番目の関数でした。

"FORCE txtval":関数の状態を。に設定しますtxtval

4

2 に答える 2

13

トップレベルのパーサーのデフォルトはサブパーサーのデフォルトをオーバーライドするため、サブパーサーのデフォルト値の設定funcは無視されますがnewstate、サブパーサーのデフォルトの値は正しいです。

サブコマンドを使用したくないと思います。サブコマンドは、選択したサブコマンドに応じて使用可能なオプションと位置引数が変わるときに使用されます。ただし、他のオプションや位置引数はありません。

次のコードは、必要なことを行うようです。

import argparse

def print_state():
    print "Print state"

def set_state(s):
    print "Setting state to " + s

parser = argparse.ArgumentParser()
parser.add_argument('state', choices = ['ON', 'OFF'], nargs='?')

args = parser.parse_args()

if args.state is None:
    print_state()
elif args.state in ('ON', 'OFF'):
    set_state(args.state)

オプションのパラメータに注意してくださいparser.add_argument。「choices」パラメーターは、「nargs」を「?」に設定しながら、使用可能なオプションを指定します。利用可能な場合は 1 つの引数を使用し、それ以外の場合は何も使用しないことを指定します。

編集:引数付きの FORCE コマンドを追加し、ON コマンドと OFF コマンドのヘルプ テキストを分けたい場合は、サブコマンドを使用する必要があります。残念ながら、デフォルトのサブコマンドを指定する方法はないようです。ただし、空の引数リストを確認し、独自の引数リストを指定することで、この問題を回避できます。私が何を意味するかを示すサンプルコードを次に示します。

import argparse
import sys

def print_state(ignored):
    print "Print state"

def set_state(s):
    print "Setting state to " + s

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
on = subparsers.add_parser('ON', help = 'On help here.')
on.set_defaults(func = set_state, newstate = 'ON')
off = subparsers.add_parser('OFF', help = 'Off help here.')
off.set_defaults(func = set_state, newstate = 'OFF')
prt = subparsers.add_parser('PRINT')
prt.set_defaults(func = print_state, newstate = 'N/A')
force = subparsers.add_parser('FORCE' , help = 'Force help here.')
force.add_argument('newstate', choices = [ 'ON', 'OFF' ])
force.set_defaults(func = set_state)

if (len(sys.argv) < 2):
    args = parser.parse_args(['PRINT'])
else:
    args = parser.parse_args(sys.argv[1:])

args.func(args.newstate)
于 2011-03-03T04:55:34.660 に答える
1

あなたのアプローチには2つの問題があります。

newstate最初に、これは sub パーサーの sub_value ではなく、 as の最上位レベルで対処する必要があることにargs既に気付いているでしょうargs.newstate。これは、デフォルトを 2 回に割り当てるnewstateと、最初の値が上書きされることを説明しているはずです。パラメータとして「ON」または「OFF」を指定してプログラムを呼び出すかどうかに関係なく、毎回set_state()が呼び出されOFFます。できるようにしたいだけpython cvsSecure ONpython cvsSecure OFF、次のようにするとうまくいきます。

from __future__ import print_function

import sys
import argparse

def set_state(state):
    print("set_state", state)

def do_on(args):
    set_state('ON')

def do_off(args):
    set_state('OFF')

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)

args = parser.parse_args()
args.func(args)

2 番目の問題は、argparseサブパーサーを単一値の引数として処理するため、呼び出す前にサブパーサーを指定する必要があることparser.parse_args()です。サブパーサー「PRINT」を追加し、 set_default_subparseradded toを使用して自動的に挿入することで、不足している引数の挿入を自動化できますargparse.ArgumentParser()(そのコードはパッケージruamel.std.argparseの一部です)。

from __future__ import print_function

import sys
import argparse

def set_default_subparser(self, name, args=None):
    """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 first position, this implies no
            # global options without a sub_parsers specified
            if args is None:
                sys.argv.insert(1, name)
            else:
                args.insert(0, name)

argparse.ArgumentParser.set_default_subparser = set_default_subparser


def print_state(args):
    print("print_state")

def set_state(state):
    print("set_state", state)

def do_on(args):
    set_state('ON')

def do_off(args):
    set_state('OFF')

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

parser_print = subparsers.add_parser('PRINT', help='default action')
parser_print.set_defaults(func=print_state)
parser_on = subparsers.add_parser('ON')
parser_on.set_defaults(func=do_on)
parser_on.add_argument('--fast', action='store_true')
parser_off = subparsers.add_parser('OFF')
parser_off.set_defaults(func=do_off)

parser.set_default_subparser('PRINT')

args = parser.parse_args()
args.func(args)

argstoなどで処理する必要はありませんがdo_on()、さまざまなサブパーサーにオプションを指定し始めると便利です。

于 2014-10-15T08:57:09.003 に答える