I would like to have a optional argument that will default to a value if only the flag is present with no value specified, but store a user-specified value instead of the default if the user specifies a value. Is there already an action available for this?
An example:
python script.py --example
# args.example would equal a default value of 1
python script.py --example 2
# args.example would equal a default value of 2
I can create an action, but wanted to see if there was an existing way to do this.
for the argument prox , I use action=’store_true’ to check if it is present or not.
I do not require any of the arguments. But, if –prox is set I require rport and lport as well. Is there an easy way of doing this with argparse without writing custom conditional coding.
More Code:
non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')
回答 0
不,argparse中没有任何选项可以构成相互包含的选项集。
解决此问题的最简单方法是:
if args.prox and(args.lport isNoneor args.rport isNone):
parser.error("--prox requires --lport and --rport.")
You’re talking about having conditionally required arguments. Like @borntyping said you could check for the error and do parser.error(), or you could just apply a requirement related to --prox when you add a new argument.
A simple solution for your example could be:
non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)
This way required receives either True or False depending on whether the user as used --prox. This also guarantees that -lport and -rport have an independent behavior between each other.
How about using parser.parse_known_args() method and then adding the --lport and --rport args as required args if --prox is present.
# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question",
usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true',
help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
# use options and namespace from first parsing
non_int.parse_args(rem_args, namespace = opts)
Also keep in mind that you can supply the namespace opts generated after the first parsing while parsing the remaining arguments the second time. That way, in the the end, after all the parsing is done, you’ll have a single namespace with all the options.
Drawbacks:
If --prox is not present the other two dependent options aren’t even present in the namespace. Although based on your use-case, if --prox is not present, what happens to the other options is irrelevant.
Need to modify usage message as parser doesn’t know full structure
--lport and --rport don’t show up in help message
回答 3
未设置lport时使用prox。如果不是,为什么不进行lport和的rport论证prox?例如
parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')
这样可以节省用户输入的时间。测试if args.prox is not None:和一样容易if args.prox:。
The accepted answer worked great for me! Since all code is broken without tests here is how I tested the accepted answer. parser.error() does not raise an argparse.ArgumentError error it instead exits the process. You have to test for SystemExit.
with pytest
import pytest
from . import parse_arguments # code that rasises parse.error()
def test_args_parsed_raises_error():
with pytest.raises(SystemExit):
parse_arguments(["argument that raises error"])
with unittests
from unittest import TestCase
from . import parse_arguments # code that rasises parse.error()
class TestArgs(TestCase):
def test_args_parsed_raises_error():
with self.assertRaises(SystemExit) as cm:
parse_arguments(["argument that raises error"])
>>> parser = argparse.ArgumentParser(prog='frobble')>>> parser.add_argument('--foo', help=argparse.SUPPRESS)>>> parser.print_help()
usage: frobble [-h]
optional arguments:-h,--help show this help message and exit
I do it by adding an option to enable the hidden ones, and grab that by looking at sysv.args.
If you do this, you have to include the special arg you pick out of sys.argv directly in the parse list if you Assume the option is -s to enable hidden options.
parser.add_argument('-a', '-axis',
dest="axis", action="store_true", default=False,
help="Rotate the earth")
if "-s" in sys.argv or "-secret" in sys.argv:
parser.add_argument('-s', '-secret',
dest="secret", action="store_true", default=False,
help="Enable secret options")
parser.add_argument('-d', '-drill',
dest="drill", action="store_true", default=False,
help="drill baby, drill")
Does this work the same for float / int / string type arguments?
I could set a default parameter and check it (e.g., set myArg = -1, or “” for a string, or “NOT_SET”). However, the value I ultimately want to use is only calculated later in the script. So I would be setting it to -1 as a default, and then updating it to something else later. This seems a little clumsy in comparison with simply checking if the value was set by the user.
回答 0
我认为,如果未提供可选参数(用指定--),None则将其初始化。因此,您可以使用进行测试is not None。请尝试以下示例:
import argparse as ap
def main():
parser = ap.ArgumentParser(description="My Script")
parser.add_argument("--myArg")
args, leftovers = parser.parse_known_args()if args.myArg isnotNone:print"myArg has been set (value is %s)"% args.myArg
I think that optional arguments (specified with --) are initialized to None if they are not supplied. So you can test with is not None. Try the example below:
import argparse as ap
def main():
parser = ap.ArgumentParser(description="My Script")
parser.add_argument("--myArg")
args, leftovers = parser.parse_known_args()
if args.myArg is not None:
print "myArg has been set (value is %s)" % args.myArg
As @Honza notes is None is a good test. It’s the default default, and the user can’t give you a string that duplicates it.
You can specify another default='mydefaultvalue, and test for that. But what if the user specifies that string? Does that count as setting or not?
You can also specify default=argparse.SUPPRESS. Then if the user does not use the argument, it will not appear in the args namespace. But testing that might be more complicated:
Internally the parser keeps a list of seen_actions, and uses it for ‘required’ and ‘mutually_exclusive’ testing. But it isn’t available to you out side of parse_args.
I think using the option default=argparse.SUPPRESS makes most sense. Then, instead of checking if the argument is not None, one checks if the argument is in the resulting namespace.
Example:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--foo", default=argparse.SUPPRESS)
ns = parser.parse_args()
print("Parsed arguments: {}".format(ns))
print("foo in namespace?: {}".format("foo" in ns))
This way, you don’t have to worry about checking by conditional is not None. You simply check for True or False. Read more about these options in the docs here
parser = argparse.ArgumentParser(description='Foo is a program that does things')
parser.add_argument('filename', nargs='?')
args = parser.parse_args()if args.filename isnotNone:print('The file name is {}'.format(args.filename))else:print('Oh well ; No args, no problems')
If your argument is positional (ie it doesn’t have a “-” or a “–” prefix, just the argument, typically a file name) then you can use the nargs parameter to do this:
parser = argparse.ArgumentParser(description='Foo is a program that does things')
parser.add_argument('filename', nargs='?')
args = parser.parse_args()
if args.filename is not None:
print('The file name is {}'.format(args.filename))
else:
print('Oh well ; No args, no problems')
回答 5
这是我的解决方案,看看我是否正在使用argparse变量
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-1","--first", required=True)
ap.add_argument("-2","--second", required=True)
ap.add_argument("-3","--third", required=False)# Combine all arguments into a list called args
args = vars(ap.parse_args())if args["third"]isnotNone:# do something
Here is my solution to see if I am using an argparse variable
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-1", "--first", required=True)
ap.add_argument("-2", "--second", required=True)
ap.add_argument("-3", "--third", required=False)
# Combine all arguments into a list called args
args = vars(ap.parse_args())
if args["third"] is not None:
# do something
This might give more insight to the above answer which I used and adapted to work for my program.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--infile', default=argparse.SUPPRESS)
args = parser.parse_args()if'infile'in args:# the argument is in the namespace, it's been provided by the user# set it to what has been provided
theinfile = args.infile
print('argument \'--infile\' was given, set to {}'.format(theinfile))else:# the argument isn't in the namespace# set it to a default value
theinfile ='your_default.txt'print('argument \'--infile\' was not given, set to default {}'.format(theinfile))
用法
$ python3 testargparse_so.py
argument '--infile' was not given, set to default your_default.txt
$ python3 testargparse_so.py --infile user_file.txt
argument '--infile' was given, set to user_file.txt
In order to address @kcpr’s comment on the (currently accepted) answer by @Honza Osobne
Unfortunately it doesn’t work then the argument got it’s default value
defined.
one can first check if the argument was provided by comparing it with the Namespace object abd providing the default=argparse.SUPPRESS option (see @hpaulj’s and @Erasmus Cedernaes answers and this python3 doc) and if it hasn’t been provided, then set it to a default value.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--infile', default=argparse.SUPPRESS)
args = parser.parse_args()
if 'infile' in args:
# the argument is in the namespace, it's been provided by the user
# set it to what has been provided
theinfile = args.infile
print('argument \'--infile\' was given, set to {}'.format(theinfile))
else:
# the argument isn't in the namespace
# set it to a default value
theinfile = 'your_default.txt'
print('argument \'--infile\' was not given, set to default {}'.format(theinfile))
Usage
$ python3 testargparse_so.py
argument '--infile' was not given, set to default your_default.txt
$ python3 testargparse_so.py --infile user_file.txt
argument '--infile' was given, set to user_file.txt
Very simple, after defining args variable by ‘args = parser.parse_args()’ it contains all data of args subset variables too. To check if a variable is set or no assuming the ‘action=”store_true” is used…
if args.argument_name:
# do something
else:
# do something else
parser.add_argument('-s',"--startdate", help="The Start Date - format YYYY-MM-DD ", required=True)
parser.add_argument('-e',"--enddate", help="The End Date format YYYY-MM-DD (Inclusive)", required=True)
parser.add_argument('-a',"--accountid", type=int, help='Account ID for the account for which data is required (Default: 570)')
parser.add_argument('-o',"--outputpath", help='Directory where output needs to be stored (Default: '+ os.path.dirname(os.path.abspath(__file__)))
I have a python script that requires some command line inputs and I am using argparse for parsing them. I found the documentation a bit confusing and couldn’t find a way to check for a format in the input parameters. What I mean by checking format is explained with this example script:
parser.add_argument('-s', "--startdate", help="The Start Date - format YYYY-MM-DD ", required=True)
parser.add_argument('-e', "--enddate", help="The End Date format YYYY-MM-DD (Inclusive)", required=True)
parser.add_argument('-a', "--accountid", type=int, help='Account ID for the account for which data is required (Default: 570)')
parser.add_argument('-o', "--outputpath", help='Directory where output needs to be stored (Default: ' + os.path.dirname(os.path.abspath(__file__)))
I need to check for option -s and -e that the input by the user is in the format YYYY-MM-DD. Is there an option in argparse that I do not know of which accomplishes this.
The type keyword argument of add_argument() allows any necessary type-checking and type conversions to be performed … type= can take any callable that takes a single string argument and returns the converted value
parser.add_argument('-s',"--startdate",
help="The Start Date - format YYYY-MM-DD",
required=True,
type=datetime.date.fromisoformat)
parser.add_argument('-e',"--enddate",
help="The End Date format YYYY-MM-DD (Inclusive)",
required=True,
type=datetime.date.fromisoformat)
For others who hit this via search engines: in Python 3.7, you can use the standard .fromisoformat class method instead of reinventing the wheel for ISO-8601 compliant dates, e.g.:
parser.add_argument('-s', "--startdate",
help="The Start Date - format YYYY-MM-DD",
required=True,
type=datetime.date.fromisoformat)
parser.add_argument('-e', "--enddate",
help="The End Date format YYYY-MM-DD (Inclusive)",
required=True,
type=datetime.date.fromisoformat)
However, my group has experienced readability issues using “store_false”. Especially when new members join our group. This is because it is most intuitive way to think is that when a user specifies an argument, the value corresponding to that argument will be True or 1.
The code reader may likely expect the logging statement to be off when the value in stop_logging is true. But code such as the following will lead to the opposite of the desired behavior –
if not stop_logging:
#log
On the other hand, if the interface is defined as the following, then the “if-statement” works and is more intuitive to read –
parser.add_argument('--stop_logging', action='store_true')
if not stop_logging:
#log
File"/usr/local/lib/python2.6/dist-packages/argparse.py", line 2169,in _get_nargs_pattern
nargs_pattern ='(-*%s-*)'%'-*'.join('A'* nargs)TypeError: can't multiply sequence by non-int of type 'str'
The script needs to take a configuration file name as an option, and user can specify whether they need to proceed totally the script or only simulate it.
The args to be passed: ./script -f config_file -s or ./script -f config_file.
It’s ok for the -f config_file part, but It keeps asking me for arguments for the -s which is optionnal and should not be followed by any.
File "/usr/local/lib/python2.6/dist-packages/argparse.py", line 2169, in _get_nargs_pattern
nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
TypeError: can't multiply sequence by non-int of type 'str'
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g","--games", type=int, default=162,
help="The number of games to simulate")
args = parser.parse_args()
The title pretty much summarizes what I’d like to have happen.
Here is what I have, and while the program doesn’t blow up on a nonpositive integer, I want the user to be informed that a nonpositive integer is basically nonsense.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--games", type=int, default=162,
help="The number of games to simulate")
args = parser.parse_args()
And the output:
python simulate_many.py -g 20
Setting up...
Playing games...
....................
Output with a negative:
python simulate_many.py -g -2
Setting up...
Playing games...
Now, obviously I could just add an if to determine if args.games is negative, but I was curious if there was a way to trap it at the argparse level, so as to take advantage of the automatic usage printing.
Ideally, it would print something similar to this:
python simulate_many.py -g a
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid int value: 'a'
def parse_args(self, args=None, namespace=None):"""Parse and validate args."""
namespace = super().parse_args(args, namespace)if namespace.games <=0:raise self.error('The number of games must be a positive integer.')return namespace
A simpler alternative, especially if subclassing argparse.ArgumentParser, is to initiate the validation from inside the parse_args method.
Inside such a subclass:
def parse_args(self, args=None, namespace=None):
"""Parse and validate args."""
namespace = super().parse_args(args, namespace)
if namespace.games <= 0:
raise self.error('The number of games must be a positive integer.')
return namespace
This technique may not be as cool as a custom callable, but it does the job.
# Custom argparse type representing a bounded intclassIntRange:def __init__(self, imin=None, imax=None):
self.imin = imin
self.imax = imaxdef __call__(self, arg):try:
value = int(arg)exceptValueError:raise self.exception()if(self.imin isnotNoneand value < self.imin)or(self.imax isnotNoneand value > self.imax):raise self.exception()return valuedef exception(self):if self.imin isnotNoneand self.imax isnotNone:return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")elif self.imin isnotNone:return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")elif self.imax isnotNone:return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")else:return argparse.ArgumentTypeError("Must be an integer")
这使您可以执行以下操作:
parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1))# Must have foo >= 1
parser.add_argument('bar', type=IntRange(1,7))# Must have 1 <= bar <= 7
变量foo现在只允许使用正整数,就像要求的OP一样。
请注意,除了上述形式以外,使用以下形式还可以设置最大值IntRange:
parser.add_argument('other', type=IntRange(imax=10))# Must have other <= 10
In case someone (like me) comes across this question in a Google search, here is an example of how to use a modular approach to neatly solve the more general problem of allowing argparse integers only in a specified range:
# Custom argparse type representing a bounded int
class IntRange:
def __init__(self, imin=None, imax=None):
self.imin = imin
self.imax = imax
def __call__(self, arg):
try:
value = int(arg)
except ValueError:
raise self.exception()
if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
raise self.exception()
return value
def exception(self):
if self.imin is not None and self.imax is not None:
return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")
elif self.imin is not None:
return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")
elif self.imax is not None:
return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")
else:
return argparse.ArgumentTypeError("Must be an integer")
This allows you to do something like:
parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1)) # Must have foo >= 1
parser.add_argument('bar', type=IntRange(1, 7)) # Must have 1 <= bar <= 7
The variable foo now allows only positive integers, like the OP asked.
Note that in addition to the above forms, just a maximum is also possible with IntRange:
parser.add_argument('other', type=IntRange(imax=10)) # Must have other <= 10
You should refactor your code and move the parsing to a function:
def parse_args(args):
parser = argparse.ArgumentParser(...)
parser.add_argument...
# ...Create your parser as you like...
return parser.parse_args(args)
Then in your main function you should just call it with:
parser = parse_args(sys.argv[1:])
(where the first element of sys.argv that represents the script name is removed to not send it as an additional switch during CLI operation.)
In your tests, you can then call the parser function with whatever list of arguments you want to test it with:
def test_parser(self):
parser = parse_args(['-l', '-m'])
self.assertTrue(parser.long)
# ...and so on.
This way you’ll never have to execute the code of your application just to test the parser.
If you need to change and/or add options to your parser later in your application, then create a factory method:
def create_parser():
parser = argparse.ArgumentParser(...)
parser.add_argument...
# ...Create your parser as you like...
return parser
You can later manipulate it if you want, and a test could look like:
# test_mock_argparse.pyimport argparsetry:from unittest import mock # python 3.3+exceptImportError:import mock # python 2.6-3.2def main():
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
const=sum, default=max,
help='sum the integers (default: find the max)')
args = parser.parse_args()print(args)# NOTE: this is how you would check what the kwargs are if you're unsurereturn args.accumulate(args.integers)@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))def test_command(mock_args):
res = main()assert res ==6,"1 + 2 + 3 = 6"if __name__ =="__main__":print(main())
“argparse portion” is a bit vague so this answer focuses on one part: the parse_args method. This is the method that interacts with your command line and gets all the passed values. Basically, you can mock what parse_args returns so that it doesn’t need to actually get values from the command line. The mockpackage can be installed via pip for python versions 2.6-3.2. It’s part of the standard library as unittest.mock from version 3.3 onwards.
You have to include all your command method’s args in Namespace even if they’re not passed. Give those args a value of None. (see the docs) This style is useful for quickly doing testing for cases where different values are passed for each method argument. If you opt to mock Namespace itself for total argparse non-reliance in your tests, make sure it behaves similarly to the actual Namespace class.
Below is an example using the first snippet from the argparse library.
# test_mock_argparse.py
import argparse
try:
from unittest import mock # python 3.3+
except ImportError:
import mock # python 2.6-3.2
def main():
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
const=sum, default=max,
help='sum the integers (default: find the max)')
args = parser.parse_args()
print(args) # NOTE: this is how you would check what the kwargs are if you're unsure
return args.accumulate(args.integers)
@mock.patch('argparse.ArgumentParser.parse_args',
return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
res = main()
assert res == 6, "1 + 2 + 3 = 6"
if __name__ == "__main__":
print(main())
Populate your arg list by using sys.argv.append() and then call
parse(), check the results and repeat.
Call from a batch/bash file with your flags and a dump args flag.
Put all your argument parsing in a separate file and in the if __name__ == "__main__": call parse and dump/evaluate the results then test this from a batch/bash file.
回答 4
我不想修改原始的服务脚本,所以我只是sys.argv在argparse中模拟了该部分。
from unittest.mock import patch
with patch('argparse._sys.argv',['python','serve.py']):...# your test code here
I did not want to modify the original serving script so I just mocked out the sys.argv part in argparse.
from unittest.mock import patch
with patch('argparse._sys.argv', ['python', 'serve.py']):
... # your test code here
This breaks if argparse implementation changes but enough for a quick test script. Sensibility is much more important than specificity in test scripts anyways.