Argparse - 如何指定默认子命令

发布于 2024-10-20 07:58:56 字数 1338 浏览 1 评论 0原文

我正在使用 Python 2.7 的 argparse 包为命令行工具编写一些选项解析逻辑。该工具应接受以下参数之一:

“ON”:打开功能。
“OFF”:关闭功能。
[未提供参数]:回显函数的当前状态。

查看 argparse 文档让我相信我想要定义两个(可能是三个)子命令,因为这三个状态是互斥的并且代表不同的概念活动。这是我目前对代码的尝试:

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

我的印象是,如果我提供 0 个参数,主解析器将设置 func=print_state,如果我提供 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 来说还是个初学者。关于如何让它发挥作用有什么建议吗?

编辑:我查看子命令的另一个原因是我正在考虑将来使用的第四个函数:

“FORCE txtval”:将函数的状态设置为txtval

I am using the argparse package of Python 2.7 to write some option-parsing logic for a command-line tool. The tool should accept one of the following arguments:

"ON": Turn a function on.
"OFF": Turn a function off.
[No arguments provided]: Echo the current state of the function.

Looking at the argparse documentation led me to believe that I wanted two--possibly three--subcommands to be defined, since these three states are mutually exclusive and represent different conceptual activities. This is my current attempt at the code:

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

I was under the impression that if I provided 0 arguments, the main parser would set func=print_state, and if I provided 1 argument, the main parser would use the appropriate subcommand's defaults and call func=set_state. Instead, I get the following error with 0 arguments:

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

And if I provide "OFF" or "ON", print_state gets called instead of set_state. If I comment out the parser.set_defaults line, set_state is called correctly.

I'm a journeyman-level programmer, but a rank beginner to Python. Any suggestions about how I can get this working?

Edit: Another reason I was looking at subcommands was a potential fourth function that I am considering for the future:

"FORCE txtval": Set the function's state to txtval.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡笑忘祈一世凡恋 2024-10-27 07:58:56

顶级解析器的默认值会覆盖子解析器的默认值,因此在子解析器上设置 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)

The defaults of the top-level parser override the defaults on the sub-parsers, so setting the default value of func on the sub-parsers is ignored, but the value of newstate from the sub-parser defaults is correct.

I don't think you want to use subcommands. Subcommands are used when the available options and positional arguments change depending on which subcommand is chosen. However, you have no other options or positional arguments.

The following code seems to do what you require:

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)

Note the optional parameters to parser.add_argument. The "choices" parameter specifies the allowable options, while setting "nargs" to "?" specifies that 1 argument should be consumed if available, otherwise none should be consumed.

Edit: If you want to add a FORCE command with an argument and have separate help text for the ON and OFF command then you do need to use subcommands. Unfortunately there doesn't seem to be a way of specifying a default subcommand. However, you can work around the problem by checking for an empty argument list and supplying your own. Here's some sample code illustrating what I mean:

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)
吃兔兔 2024-10-27 07:58:56

您的方法有两个问题。

首先,您可能已经注意到 newstate 不是子解析器的某个 sub_value,需要在 args 的顶层作为 args.newstate 进行处理>。这应该可以解释为 newstate 分配默认值两次将导致第一个值被覆盖。无论您使用“ON”还是“OFF”作为参数调用程序,每次 set_state() 都会以 OFF 进行调用。如果您只想能够执行 python cvsSecure ON 和
python 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)

第二个问题是 argparse 确实将子解析器作为单值参数处理,因此您必须在调用 parser.parse_args 之前指定一个()。您可以通过添加额外的子解析器“PRINT”并自动插入来自动插入缺少的参数
使用 set_default_subparser 添加到 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)

您不需要在 args< 中处理/code> 到 do_on() 等,但如果您开始为不同的子解析器指定选项,它就会派上用场。

There are two problems with your approach.

First you probably already noticed that newstate is not some sub_value of the sub parser and needs to be addressed at the top level of args as args.newstate. That should explain that assigning a default to newstate twice will result in the first value being overwritten. Whether you call your programm with 'ON' or 'OFF' as a parameter, each time set_state() will be called with OFF. If you just want to be able to do python cvsSecure ON and
python cvsSecure OFF the following would work:

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)

The second problem is that argparse does handle subparsers as single value arguments, so you have to specify one before invoking parser.parse_args(). You can automate insertion of a lacking argument by adding a extra subparser 'PRINT' and automatically inserting
that using set_default_subparser added to argparse.ArgumentParser() (that code is part
of the package 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)

You don't need to handle in args to do_on(), etc., but it comes in handy if you start specifying options to the different subparsers.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文