Python argparse: Make at least one argument requir

2020-02-07 17:21发布

I've been using argparse for a Python program that can -process, -upload or both:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

The program is meaningless without at least one parameter. How can I configure argparse to force at least one parameter to be chosen?

UPDATE:

Following the comments: What's the Pythonic way to parametrize a program with at least one option?

11条回答
Anthone
2楼-- · 2020-02-07 17:42

If you require a python program to run with at least one parameter, add an argument that doesn't have the option prefix (- or -- by default) and set nargs=+ (Minimum of one argument required). The problem with this method I found is that if you do not specify the argument, argparse will generate a "too few arguments" error and not print out the help menu. If you don't need that functionality, here's how to do it in code:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

I think that when you add an argument with the option prefixes, nargs governs the entire argument parser and not just the option. (What I mean is, if you have an --option flag with nargs="+", then --option flag expects at least one argument. If you have option with nargs="+", it expects at least one argument overall.)

查看更多
家丑人穷心不美
3楼-- · 2020-02-07 17:45
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')
查看更多
forever°为你锁心
4楼-- · 2020-02-07 17:45

If not the 'or both' part (I have initially missed this) you could use something like this:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Though, probably it would be a better idea to use subcommands instead.

查看更多
一夜七次
5楼-- · 2020-02-07 17:46

Maybe use sub-parsers?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

Now --help shows:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

You can add additional options to these sub-parsers as well. Also instead of using that dest='subparser_name' you can also bind functions to be directly called on given sub-command (see docs).

查看更多
Lonely孤独者°
6楼-- · 2020-02-07 17:47

Use append_const to a list of actions and then check that the list is populated:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

You can even specify the methods directly within the constants.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
查看更多
smile是对你的礼貌
7楼-- · 2020-02-07 17:58

For http://bugs.python.org/issue11588 I am exploring ways of generalizing the mutually_exclusive_group concept to handle cases like this.

With this development argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py I am able to write:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

which produces the following help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

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

possible actions (at least one is required):
  -p, --process
  -u, --upload

This accepts inputs like '-u', '-up', '--proc --up' etc.

It ends up running a test similar to https://stackoverflow.com/a/6723066/901925, though the error message needs to be clearer:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

I wonder:

  • are the parameters kind='any', required=True clear enough (accept any of the group; at least one is required)?

  • is usage (-p | -u) clear? A required mutually_exclusive_group produces the same thing. Is there some alternative notation?

  • is using a group like this more intuitive than phihag's simple test?

查看更多
登录 后发表回答