How to create an argument that is optional?

青春壹個敷衍的年華 提交于 2019-12-11 02:47:46

问题


Instead of the user having to use script.py --file c:/stuff/file.txt is there a way to let the user optionally use the --file? So instead, it would look like script.py c:/stuff/file.txt but the parser would still know that the user is referring to the --file argument (because it's implied).


回答1:


Try this

import argparse

class DoNotReplaceAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if not getattr(namespace, self.dest):
            setattr(namespace, self.dest, values)

parser = argparse.ArgumentParser(description="This is an example.")
parser.add_argument('file', nargs='?', default='', help='specifies a file.', action=DoNotReplaceAction)
parser.add_argument('--file', help='specifies a file.')

args = parser.parse_args()
# check for file argument
if not args.file:
    raise Exception('Missing "file" argument')

Look at help message. All arguments are optional

usage: test.py [-h] [--file FILE] [file]

This is an example.

positional arguments:
  file         specifies a file.

optional arguments:
  -h, --help   show this help message and exit
  --file FILE  specifies a file.

One thing to notice is that positional file will override optional --file and set args.file to default ''. To overcome this I used custom action for positional file. It forbids overriding already set properties.

The other thing to notice is rather than raising an Exception you could specify default value.




回答2:


If I may rephrase your question into the answer, you want a script which, when run as:

  • script blah treats blah as a file name to be opened
  • script --file blah treats blah as a file name to be opened
  • script --file blah eggs treats blah as a file name to be opened, and eggs ... how?
  • script blah eggs treats blah ... differently? how?

In any case, I'd still start with argparse:

#! /usr/bin/env python

import argparse

parser = argparse.ArgumentParser(description='script to morgle blahs')
parser.add_argument('--file', help='specify file name to be opened')
parser.add_argument('args', metavar='FILE', nargs='*')
args = parser.parse_args()

print args

At this point running ./script.py -h produces:

usage: script.py [-h] [--file FILE] [FILE [FILE ...]]

script to morgle blahs

positional arguments:
  FILE

optional arguments:
  -h, --help   show this help message and exit
  --file FILE  specify file name to be opened

Additional runs:

$ ./script.py
Namespace(args=[], file=None)
$ ./script.py blah
Namespace(args=['blah'], file=None)
$ ./script.py --file blah eggs
Namespace(args=['eggs'], file='blah')
$ ./script.py blah eggs
Namespace(args=['blah', 'eggs'], file=None)

So, instead of simply print args, now you can test whether args.file is None (no --file) and then check args.args, and if args.file is not None, you can still check args.args.

If, at some point, you decide in your own code that some combination of arguments is bad/invalid, you can call parser.error, e.g.:

if args.file is not None and len(args.args) > 0:
    parser.error('use [--file] <filename>, not --file <filename1> <filename2>')
if args.file is None and len(args.args) != 1:
    parser.error('use [--file] <filename>')

would demand exactly one argument, whether or not preceded by the --file string.




回答3:


To accept either --file FILE or just FILE, you could use mutually_exclusive_group():

import argparse

parser = argparse.ArgumentParser(prog='script',
                                 description="This is an example.",
                                 usage='%(prog)s [-h] (--file FILE | FILE)')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('positional_file', nargs='?', help='specifies a file.')
group.add_argument('--file', help='specifies a file.')

args = parser.parse_args()
print(args)
filename = args.positional_file if args.file is None else args.file

Examples

['abc'] -> Namespace(file=None, positional_file='abc')

['--file', 'abc'] -> Namespace(file='abc', positional_file=None)

['--file', 'abc', 'def'] -> usage: script [-h] (--file FILE | FILE)
script: error: argument positional_file: not allowed with argument --file

[] -> usage: script [-h] (--file FILE | FILE)
script: error: one of the arguments positional_file --file is required



回答4:


You could use the required=True flag in argparse:

import argparse

parser = argparse.ArgumentParser(description="Describe stuff.")
parser.add_argument('--foo', required=True, help='bar')

However, as this documentation says it is considered bad form to make options required, since users expect options to be optional.

You could instead define your required argument and then have the optional --foo flag store to this required argument. This might cause the parser to throw an exception though since it may think that you are just ignoring the required argument.

import argparse

parser = argparse.ArgumentParser(description="Will this work?")
parser.add_argument('bar', help="required argument")
parser.add_argument('--foo', required=False, help="kind of required argument", dest='bar')

I think that the best answer is to just not have a sort-of-required flag. Just make the variable required and define a default value for it in the event that you need something there to use in your program but a default value is applicable somehow:

import argparse

parser = argparse.ArgumentParser(description="Other option.")
parser.add_argument('bar', default='value', help="required argument")



回答5:


In your design, there's a principal ambiguity (that's already a reasonable explanation why it isn't implemented by argparse):

  • If there are more positional arguments after such a "dual-mode" one (say, foo/--foo and bar) which one should a positional argument be assigned to in a cmdline like --foo=foo bar? This becomes even more confusing if there are multiple "dual-mode" args.

In my script I wrote two years ago that used "dual-mode" arguments, I prohibited such input altogether, requiring that "positional-mode" arguments, if any, come first, followed by "named-mode" ones.

The script was in Perl and I used custom logic to implement this after parsing other options with Perl's Getopt::Long in "pass-through" mode (it passes through any arguments that aren't recognized).

So I suggest you do the same, using ArgumentParser.parse_known_args().



来源:https://stackoverflow.com/questions/17707234/how-to-create-an-argument-that-is-optional

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!