问题
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
treatsblah
as a file name to be openedscript --file blah
treatsblah
as a file name to be openedscript --file blah eggs
treatsblah
as a file name to be opened, andeggs
... how?script blah eggs
treatsblah
... 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
andbar
) 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