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条回答
我命由我不由天
2楼-- · 2020-02-07 17:58

This achieves the purpose and this will also be relfected in the argparse autogenerated --help output, which is imho what most sane programmers want (also works with optional arguments):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Official docs on this: https://docs.python.org/3/library/argparse.html#choices

查看更多
再贱就再见
3楼-- · 2020-02-07 18:00
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')
查看更多
Rolldiameter
4楼-- · 2020-02-07 18:03

The best way to do this is by using python inbuilt module add_mutually_exclusive_group.

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

If you want only one argument to be selected by command line just use required=True as an argument for group

group = parser.add_mutually_exclusive_group(required=True)
查看更多
Deceive 欺骗
5楼-- · 2020-02-07 18:05

Requirements Review

  • use argparse (I will ignore this one)
  • allow one or two actions to be called (at least one required).
  • try to by Pythonic (I would rather call it "POSIX"-like)

There are also some implicit requirements when living on command line:

  • explain the usage to the user in a way which is easy to understand
  • options shall be optional
  • allow specifying flags and options
  • allow combining with other parameters (like file name or names).

Sample solution using docopt (file managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Try to run it:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Show the help:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

And use it:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Short alternative short.py

There can be even shorter variant:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Usage looks like this:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Note, that instead of boolean values for "process" and "upload" keys there are counters.

It turns out, we cannot prevent duplication of these words:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Conclusions

Designing good command line interface can be challenging sometime.

There are multiple aspects of command line based program:

  • good design of command line
  • selecting/using proper parser

argparse offers a lot, but restricts possible scenarios and can become very complex.

With docopt things go much shorter while preserving readability and offering high degree of flexibility. If you manage getting parsed arguments from dictionary and do some of conversions (to integer, opening files..) manually (or by other library called schema), you may find docopt good fit for command line parsing.

查看更多
混吃等死
6楼-- · 2020-02-07 18:06

I know this is old as dirt, but the way to require one option but forbid more than one (XOR) is like this:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Output:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  
查看更多
登录 后发表回答