Python command line arguments check if default or given - python

Here is my code portion:
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store', dest='xxx', default = 'ABC')
parser.add_argument('-b', action='store', dest='yyy')
parser.add_argument('-c', action='store', dest='zzz')
args = parser.parse_args()
I want the code to work like this:
If b and c are given, do command2. Otherwise, do command1
if -a argument is given, then adding -b or -c throws an error
I tried this way:
if args.xxx and (args.yyy or args.zzz):
parser.print_help()
sys.exit()
But it didn't worked, because '-a' always has a deafult value and i can't change it.
How can i fix it?

Here's one way to do it:
# If option xxx is not the default, yyy and zzz should not be present.
if args.xxx != 'ABC' and (args.yyy or args.zzz):
# Print help, exit.
# Options yyy and zzz should both be either present or None.
if (args.yyy is None) != (args.zzz is None):
# Print help, exit.
# Earn our pay.
if args.yyy is None:
command2()
else:
command1()
You might also consider a usage pattern based on subcommands, as noted in the comment by user toine.

I would use:
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='xxx')
parser.add_argument('-b', dest='yyy')
parser.add_argument('-c', dest='zzz')
args = parser.parse_args()
if args.xxx is None:
args.xxx = 'ABC'
else:
if args.zzz is not None or args.yyy is not None:
parser.error('cannot use "b" or "c" with "a"')
if args.zzz is not None and args.yyy is not None:
command2()
else:
command1()
Testing for None is the surest way of testing whether the argument was given or not (though the simpler truth test is nearly as good). Internally parse_args keeps a list of seen_actions, but that isn't available to the user. In http://bugs.python.org/issue11588 there's a proposal to provide a testing hook that would have access to this list.

Related

Handling invalid and empty arguments while using optional argparse arguments

Below is an example code that uses argparse
import os
import numpy
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-C','--Chk',type=str, help='Choose arg')
parser.add_argument('-R','--ReC',type=str, help='Choose arg')
args = vars(parser.parse_args())
if args['Chk'] == 'compo1':
print('This is comp1')
elif args['Chk'] == 'compo2':
print('This is comp2')
else:
print('The specified comp does not exist')
if args['ReC'] == 'recompo':
print('This is second test')
else:
print('The specified second_T does not exist')
if __name__=='__main__':
main()
The above code works fine. Since both are optional arguments, I would like to have two features:
If invalid arguments are given, for -C or -R I would like to print/raise a message. I tried using raise argparse.ArgumentTypeError, see below.
if len(args) > 8 or len(args) < 3:
raise argparse.ArgumentTypeError('Print this error message')
return
Secondly, I would like to have situations where the code should not do anything if either of -C or -R are not given. In the above code, if no arguments are given in either case, it prints The specified comp does not exist which is not ideal.
Any better way to do the above tasks ? Thanks
If you use choices, argparse will test for a specific set of values:
In [44]: parser = argparse.ArgumentParser()
...: parser.add_argument('-C','--Chk',choices=['compo1','compo2'], help='Choose arg', default='foobar')
...: parser.add_argument('-R','--ReC',choices=['recompo'], help='Choose arg', default='xxx');
Acceptable:
In [45]: parser.parse_args('-C compo1 -R recompo'.split())
Out[45]: Namespace(Chk='compo1', ReC='recompo')
Defaults - I specified some strings; default default is None:
In [46]: parser.parse_args([])
Out[46]: Namespace(Chk='foobar', ReC='xxx')
A wrong choice raises an error with usage and exit:
In [47]: parser.parse_args('-C compo1 -R recomp1'.split())
usage: ipykernel_launcher.py [-h] [-C {compo1,compo2}] [-R {recompo}]
ipykernel_launcher.py: error: argument -R/--ReC: invalid choice: 'recomp1' (choose from 'recompo')
A type function could be used instead if you want to limit the string lengths instead.
Otherwise, post-parsing testing of values is best, even if the logic looks a bit messy.
for error handling, you can check this way:
class ArgumentParserError(Exception): pass
class ThrowingArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise ArgumentParserError(message)
and this is maybe some help for you:
parser = ThrowingArgumentParser(description="YOUR DESCRIPTION")
parser.add_argument('func', nargs='?', choices=['C','R'], const='')

How to pass None keyword as command line argument

I am trying to pass None keyword as a command line parameter to a script as follows, if I explicity mention Category=None it works but the moment I switch to sys.argv[1] it fails, any pointers on how to fix this?
category = None --> works
#category=sys.argv[1] --> doesn't work
so I tried as below which still didn't work
if sys.argv[1].strip()==None:
category = None
else:
category=sys.argv[1].strip()
Command line passage:
script.py None
As Stephen mentioned, None is not a string, so when comparing a str type to a NoneType type, the result will always be False.
If I just put " around None on the right-hand-side of the comparison, we get:
import sys
if sys.argv[1].strip() == "None":
category = None
else:
category = sys.argv[1].strip()
print(category)
print(type(category))
Resulting in:
~/temp $ python script.py 123
123
<type 'str'>
~/temp $ python script.py None
None
<type 'NoneType'>
argparse instead?
However, I recommend using argparse instead, if you're in a position to do so. I use it all the time.
The above code could be replaced with:
import argparse
parser = argparse.ArgumentParser(description='Do some cool stuff.')
def none_or_str(value):
if value == 'None':
return None
return value
parser.add_argument('category', type=none_or_str, nargs='?', default=None,
help='the category of the stuff')
args = parser.parse_args()
print(args.category)
print(type(args.category))
Which will also tolerate no parameters
~/temp $ python script.py
None
<type 'NoneType'>
~/temp $ python script.py 123
123
<type 'str'>
~/temp $ python script.py None
None
<type 'NoneType'>
And it auto-formats some help text for you!
~/temp $ python script.py --help
usage: script.py [-h] [category]
Do some cool stuff.
positional arguments:
category the category of the stuff
optional arguments:
-h, --help show this help message and exit
just an FYI. You can use ast module to convert sting 'None' to None type
Ex:
import ast
if not ast.literal_eval(sys.argv[1]):
print "Input Arg is None"
or
if sys.argv[1].strip() == 'None':
print "Input Arg is None"
Faster and better solution.
I think more appropriate way to do this would be to check if there's any parameter passed or not. If not then set category to None, otherwise set the value that is passed.
Ex:
if len(sys.argv) == 2: // or >= 2 in case of more parameter
category = sys.argv[1].strip()
else:
category = None
So if you just call script.py, category will be set to None.
If you call like script.py Value, category will be set to Value.
Piggybacking on #FraggaMuffin's answer, you may use a lambda function for convenience. Thus, instead of
parser.add_argument('category', type=none_or_str, nargs='?', default=None,
help='the category of the stuff')
You may write
parser.add_argument('category', type=lambda x : None if x == 'None' else str(x), nargs='?', default=None,
help='the category of the stuff')
This is for convenience, if you do not want to write separate functions such as none_or_str, none_or_int, etc.

python optparse, default values for optional options

This is more like a code design question. what are good default values for optional options that are of type string/directory/fullname of files?
Let us say I have code like this:
import optparse
parser = optparse.OptionParser()
parser.add_option('-i', '--in_dir', action = "store", default = 'n', help = 'this is an optional arg')
(options, args) = parser.parse_args()
Then I do:
if options.in_dir == 'n':
print 'the user did not pass any value for the in_dir option'
else:
print 'the user in_dir=%s' %(options.in_dir)
Basically I want to have default values that mean the user did not input such option versus the actual value. Using 'n' was arbitrary, is there a better recommendation?
You could use an empty string, "", which Python interprets as being False; you can simply test:
if options.in_dir:
# argument supplied
else:
# still empty, no arg
Alternatively, use None:
if options.in_dir is None:
# no arg
else:
# arg supplied
Note that the latter is, per the documentation the default for un-supplied arguments.
How about just None?
Nothing mandates that the default values must be of the same type as the option itself.
import optparse
parser = optparse.OptionParser()
parser.add_option('-i', '--in_dir', default=None, help='this is an optional arg')
(options, args) = parser.parse_args()
print vars(options)
(ps. action="store" isn't required; store is the default action.)

Parsing boolean values with argparse

I would like to use argparse to parse boolean command-line arguments written as "--foo True" or "--foo False". For example:
my_program --my_boolean_flag False
However, the following test code does not do what I would like:
import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)
Sadly, parsed_args.my_bool evaluates to True. This is the case even when I change cmd_line to be ["--my_bool", ""], which is surprising, since bool("") evalutates to False.
How can I get argparse to parse "False", "F", and their lower-case variants to be False?
I think a more canonical way to do this is via:
command --feature
and
command --no-feature
argparse supports this version nicely:
Python 3.9+:
parser.add_argument('--feature', action=argparse.BooleanOptionalAction)
Python < 3.9:
parser.add_argument('--feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
Of course, if you really want the --arg <True|False> version, you could pass ast.literal_eval as the "type", or a user defined function ...
def t_or_f(arg):
ua = str(arg).upper()
if 'TRUE'.startswith(ua):
return True
elif 'FALSE'.startswith(ua):
return False
else:
pass #error condition maybe?
Yet another solution using the previous suggestions, but with the "correct" parse error from argparse:
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
This is very useful to make switches with default values; for instance
parser.add_argument("--nice", type=str2bool, nargs='?',
const=True, default=False,
help="Activate nice mode.")
allows me to use:
script --nice
script --nice <bool>
and still use a default value (specific to the user settings). One (indirectly related) downside with that approach is that the 'nargs' might catch a positional argument -- see this related question and this argparse bug report.
If you want to allow --feature and --no-feature at the same time (last one wins)
This allows users to make a shell alias with --feature, and overriding it with --no-feature.
Python 3.9 and above
parser.add_argument('--feature', default=True, action=argparse.BooleanOptionalAction)
Python 3.8 and below
I recommend mgilson's answer:
parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
If you DON'T want to allow --feature and --no-feature at the same time
You can use a mutually exclusive group:
feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)
You can use this helper if you are going to set many of them:
def add_bool_arg(parser, name, default=False):
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--' + name, dest=name, action='store_true')
group.add_argument('--no-' + name, dest=name, action='store_false')
parser.set_defaults(**{name:default})
add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')
Here is another variation without extra row/s to set default values. The boolean value is always assigned, so that it can be used in logical statements without checking beforehand:
import argparse
parser = argparse.ArgumentParser(description="Parse bool")
parser.add_argument("--do-something", default=False, action="store_true",
help="Flag to do something")
args = parser.parse_args()
if args.do_something:
print("Do something")
else:
print("Don't do something")
print(f"Check that args.do_something={args.do_something} is always a bool.")
oneliner:
parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
There seems to be some confusion as to what type=bool and type='bool' might mean. Should one (or both) mean 'run the function bool(), or 'return a boolean'? As it stands type='bool' means nothing. add_argument gives a 'bool' is not callable error, same as if you used type='foobar', or type='int'.
But argparse does have registry that lets you define keywords like this. It is mostly used for action, e.g. `action='store_true'. You can see the registered keywords with:
parser._registries
which displays a dictionary
{'action': {None: argparse._StoreAction,
'append': argparse._AppendAction,
'append_const': argparse._AppendConstAction,
...
'type': {None: <function argparse.identity>}}
There are lots of actions defined, but only one type, the default one, argparse.identity.
This code defines a 'bool' keyword:
def str2bool(v):
#susendberg's function
return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool') # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)
parser.register() is not documented, but also not hidden. For the most part the programmer does not need to know about it because type and action take function and class values. There are lots of stackoverflow examples of defining custom values for both.
In case it isn't obvious from the previous discussion, bool() does not mean 'parse a string'. From the Python documentation:
bool(x): Convert a value to a Boolean, using the standard truth testing procedure.
Contrast this with
int(x): Convert a number or string x to an integer.
Simplest & most correct way is:
from distutils.util import strtobool
parser.add_argument('--feature', dest='feature',
type=lambda x: bool(strtobool(x)))
Do note that True values are y, yes, t, true, on and 1;
false values are n, no, f, false, off and 0. Raises ValueError if val is anything else.
A quite similar way is to use:
feature.add_argument('--feature',action='store_true')
and if you set the argument --feature in your command
command --feature
the argument will be True, if you do not set type --feature the arguments default is always False!
I was looking for the same issue, and imho the pretty solution is :
def str2bool(v):
return v.lower() in ("yes", "true", "t", "1")
and using that to parse the string to boolean as suggested above.
This works for everything I expect it to:
add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([]) # Whatever the default was
parser.parse_args(['--foo']) # True
parser.parse_args(['--nofoo']) # False
parser.parse_args(['--foo=true']) # True
parser.parse_args(['--foo=false']) # False
parser.parse_args(['--foo', '--nofoo']) # Error
The code:
def _str_to_bool(s):
"""Convert string to bool (in argparse context)."""
if s.lower() not in ['true', 'false']:
raise ValueError('Need bool; got %r' % s)
return {'true': True, 'false': False}[s.lower()]
def add_boolean_argument(parser, name, default=False):
"""Add a boolean argument to an ArgumentParser instance."""
group = parser.add_mutually_exclusive_group()
group.add_argument(
'--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
group.add_argument('--no' + name, dest=name, action='store_false')
Simplest. It's not flexible, but I prefer simplicity.
parser.add_argument('--boolean_flag',
help='This is a boolean flag.',
type=eval,
choices=[True, False],
default='True')
EDIT: If you don't trust the input, don't use eval.
In addition to what #mgilson said, it should be noted that there's also a ArgumentParser.add_mutually_exclusive_group(required=False) method that would make it trivial to enforce that --flag and --no-flag aren't used at the same time.
This is actually outdated. For Python 3.7+, Argparse now supports boolean args (search BooleanOptionalAction).
The implementation looks like this:
import argparse
ap = argparse.ArgumentParser()
# List of args
ap.add_argument('--foo', default=True, type=bool, help='Some helpful text that is not bar. Default = True')
# Importable object
args = ap.parse_args()
One other thing to mention: this will block all entries other than True and False for the argument via argparse.ArgumentTypeError. You can create a custom error class for this if you want to try to change this for any reason.
A simpler way would be to use as below.
parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])
After previously following #akash-desarda 's excellence answer https://stackoverflow.com/a/59579733/315112 , to use strtobool via lambda, later, I decide to use strtobool directly instead.
import argparse
from distutils import util
parser.add_argument('--feature', type=util.strtobool)
Yes you're right, strtobool is returning an int, not a bool. But strtobool will not returning any other value except 0 and 1, and python will get them converted to a bool value seamlessy and consistently.
>>> 0 == False
True
>>> 0 == True
False
>>> 1 == False
False
>>> 1 == True
True
While on receiving a wrong input value like
python yours.py --feature wrong_value
An argparse.Action with strtobool compared to lambda will produce a slightly clearer/comprehensible error message:
yours.py: error: argument --feature: invalid strtobool value: 'wrong_value'
Compared to this code,
parser.add_argument('--feature', type=lambda x: bool(util.strtobool(x))
Which will produce a less clear error message:
yours.py: error: argument --feature: invalid <lambda> value: 'wrong_value'
Simplest way would be to use choices:
parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))
args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)
Not passing --my-flag evaluates to False. The required=True option could be added if you always want the user to explicitly specify a choice.
As an improvement to #Akash Desarda 's answer, you could do
import argparse
from distutils.util import strtobool
parser = argparse.ArgumentParser()
parser.add_argument("--foo",
type=lambda x:bool(strtobool(x)),
nargs='?', const=True, default=False)
args = parser.parse_args()
print(args.foo)
And it supports python test.py --foo
(base) [costa#costa-pc code]$ python test.py
False
(base) [costa#costa-pc code]$ python test.py --foo
True
(base) [costa#costa-pc code]$ python test.py --foo True
True
(base) [costa#costa-pc code]$ python test.py --foo False
False
Expanding on gerardw's answer
The reason parser.add_argument("--my_bool", type=bool) doesn't work is that bool("mystring") is True for any non-empty string so bool("False") is actually True.
What you want is
my_program.py
import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument(
"--my_bool",
choices=["False", "True"],
)
parsed_args = parser.parse_args()
my_bool = parsed_args.my_bool == "True"
print(my_bool)
$ python my_program.py --my_bool False
False
$ python my_program.py --my_bool True
True
$ python my_program.py --my_bool true
usage: my_program.py [-h] [--my_bool {False,True}]
my_program.py: error: argument --my_bool: invalid choice: 'true' (choose from 'False', 'True')
I think the most canonical way will be:
parser.add_argument('--ensure', nargs='*', default=None)
ENSURE = config.ensure is None
I found good way to store default value of parameter as False and when it is present in commandline argument then its value should be true.
cmd command
when you want argument to be true:
python main.py --csv
when you want your argument should be false:
python main.py
import argparse
from ast import parse
import sys
parser = argparse.ArgumentParser(description='')
parser.add_argument('--csv', action='store_true', default = False
,help='read from csv')
args = parser.parse_args()
if args.csv:
print('reading from csv')
Quick and easy, but only for arguments 0 or 1:
parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)
The output will be "False" after calling from terminal:
python myscript.py 0
class FlagAction(argparse.Action):
# From http://bugs.python.org/issue8538
def __init__(self, option_strings, dest, default=None,
required=False, help=None, metavar=None,
positive_prefixes=['--'], negative_prefixes=['--no-']):
self.positive_strings = set()
self.negative_strings = set()
for string in option_strings:
assert re.match(r'--[A-z]+', string)
suffix = string[2:]
for positive_prefix in positive_prefixes:
self.positive_strings.add(positive_prefix + suffix)
for negative_prefix in negative_prefixes:
self.negative_strings.add(negative_prefix + suffix)
strings = list(self.positive_strings | self.negative_strings)
super(FlagAction, self).__init__(option_strings=strings, dest=dest,
nargs=0, const=None, default=default, type=bool, choices=None,
required=required, help=help, metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.positive_strings:
setattr(namespace, self.dest, True)
else:
setattr(namespace, self.dest, False)
Similar to #Akash but here is another approach that I've used. It uses str than lambda because python lambda always gives me an alien-feelings.
import argparse
from distutils.util import strtobool
parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=str, default="False")
args = parser.parse_args()
if bool(strtobool(args.my_bool)) is True:
print("OK")
just do the following , you can make --test = True by using
python filename --test
parser.add_argument("--test" , default=False ,help="test ?", dest='test', action='store_true')
Convert the value:
def __arg_to_bool__(arg):
"""__arg_to_bool__
Convert string / int arg to bool
:param arg: argument to be converted
:type arg: str or int
:return: converted arg
:rtype: bool
"""
str_true_values = (
'1',
'ENABLED',
'ON',
'TRUE',
'YES',
)
str_false_values = (
'0',
'DISABLED',
'OFF',
'FALSE',
'NO',
)
if isinstance(arg, str):
arg = arg.upper()
if arg in str_true_values:
return True
elif arg in str_false_values:
return False
if isinstance(arg, int):
if arg == 1:
return True
elif arg == 0:
return False
if isinstance(arg, bool):
return arg
# if any other value not covered above, consider argument as False
# or you could just raise and error
return False
[...]
args = ap.parse_args()
my_arg = options.my_arg
my_arg = __arg_to_bool__(my_arg)
You can create a BoolAction and then use it
class BoolAction(Action):
def __init__(
self,
option_strings,
dest,
nargs=None,
default: bool = False,
**kwargs,
):
if nargs is not None:
raise ValueError('nargs not allowed')
super().__init__(option_strings, dest, default=default, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
input_value = values.lower()
b = input_value in ['true', 'yes', '1']
if not b and input_value not in ['false', 'no', '0']:
raise ValueError('Invalid boolean value "%s".)
setattr(namespace, self.dest, b)
and then set action=BoolAction in parser.add_argument()

python argparse - optional append argument with choices

I have a script where I ask the user for a list of pre-defined actions to perform. I also want the ability to assume a particular list of actions when the user doesn't define anything. however, it seems like trying to do both of these together is impossible.
when the user gives no arguments, they receive an error that the default choice is invalid
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args([])
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]]
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock')
and when they do define a set of actions, the resultant namespace has the user's actions appended to the default, rather than replacing the default
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args(['lock'])
args
>>> Namespace(action=[['dump', 'clear'], ['dump']])
What you need can be done using a customized argparse.Action as in the following example:
import argparse
parser = argparse.ArgumentParser()
class DefaultListAction(argparse.Action):
CHOICES = ['clear','copy','dump','lock']
def __call__(self, parser, namespace, values, option_string=None):
if values:
for value in values:
if value not in self.CHOICES:
message = ("invalid choice: {0!r} (choose from {1})"
.format(value,
', '.join([repr(action)
for action in self.CHOICES])))
raise argparse.ArgumentError(self, message)
setattr(namespace, self.dest, values)
parser.add_argument('actions', nargs='*', action=DefaultListAction,
default = ['dump', 'clear'],
metavar='ACTION')
print parser.parse_args([])
print parser.parse_args(['lock'])
The output of the script is:
$ python test.py
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])
In the documentation (http://docs.python.org/dev/library/argparse.html#default), it is said :
For positional arguments with nargs equal to ? or *, the default value is used when no command-line argument was present.
Then, if we do :
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts, default='clear')
print p.parse_args([])
We get what we expect
Namespace(action='clear')
The problem is when you put a list as a default.
But I've seen it in the doc,
parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')
So, I don't know :-(
Anyhow, here is a workaround that does the job you want :
import sys, argparse
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts)
args = ['dump', 'clear'] # I set the default here ...
if sys.argv[1:]:
args = p.parse_args()
print args
I ended up doing the following:
no append
add the empty list to the possible choices or else the empty input breaks
without default
check for an empty list afterwards and set the actual default in that case
Example:
parser = argparse.ArgumentParser()
parser.add_argument(
'is',
type=int,
choices=[[], 1, 2, 3],
nargs='*',
)
args = parser.parse_args(['1', '3'])
assert args.a == [1, 3]
args = parser.parse_args([])
assert args.a == []
if args.a == []:
args.a = [1, 2]
args = parser.parse_args(['1', '4'])
# Error: '4' is not valid.
You could test whether the user is supplying actions (in which case parse it as a required, position argument), or is supplying no actions (in which case parse it as an optional argument with default):
import argparse
import sys
acts = ['clear', 'copy', 'dump', 'lock']
p = argparse.ArgumentParser()
if sys.argv[1:]:
p.add_argument('action', nargs = '*', choices = acts)
else:
p.add_argument('--action', default = ['dump', 'clear'])
args = p.parse_args()
print(args)
when run, yields these results:
% test.py
Namespace(action=['dump', 'clear'])
% test.py lock
Namespace(action=['lock'])
% test.py lock dump
Namespace(action=['lock', 'dump'])
You probably have other options to parse as well. In that case, you could use parse_known_args to parse the other options, and then handle the unknown arguments in a second pass:
import argparse
acts = ['clear', 'copy', 'dump', 'lock']
p = argparse.ArgumentParser()
p.add_argument('--foo')
args, unknown = p.parse_known_args()
if unknown:
p.add_argument('action', nargs = '*', choices = acts)
else:
p.add_argument('--action', default = ['dump', 'clear'])
p.parse_args(unknown, namespace = args)
print(args)
when run, yields these results:
% test.py
Namespace(action=['dump', 'clear'], foo=None)
% test.py --foo bar
Namespace(action=['dump', 'clear'], foo='bar')
% test.py lock dump
Namespace(action=['lock', 'dump'], foo=None)
% test.py lock dump --foo bar
Namespace(action=['lock', 'dump'], foo='bar')
The action was being appended because of the "action='append'" parameter you passed to argparse.
After removing this parameter, the arguments passed by a user would be displayed on their own, but the program would throw an error when no arguments were passed.
Adding a '--' prefix to the first parameter resolves this in the laziest way.
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('--action', nargs='*', choices=acts, default=[['dump', 'clear']])
args = p.parse_args()
The downside to this approach is that the options passed by the user must now be preceded by '--action', like:
app.py --action clear dump copy

Categories