argparse in Python - python

I came across this Python script:
parser = ap.ArgumentParser()
parser.add_argument("-t", "--trainingSet", help="Path to Training Set", required="True")
args = vars(parser.parse_args())
train_path = args["trainingSet"]
The points I didn't get are:
How do we use those arguments in the command line: "-t", "--trainingSet", help="Path to Training Set", required="True"?
What does args mean? How was the training path retrieved?
Thanks.

Create a parser object:
parser = ap.ArgumentParser()
add an argument definition to the parser (it creates an Action object, though you don't need to worry about that here).
parser.add_argument("-t", "--trainingSet", help="Path to Training Set", required="True")
Tell the parser to parse the commandline arguments that are available in sys.argv. This a list of strings created by the commandline shell (bash or dos).
args = parser.parse_args()
args is a argparse.Namespace object. It is a simple object class. vars converts it to a dictionary
argdict = vars(args)
This is ordinary dictionary access
train_path = argdict["trainingSet"]
you can get the same thing from the namespace
train_path = args.trainingSet
I'd recommend looking at args
print args
With this parser definition, a commandline like
$ python myprog.py -t astring # or
$ python myprog.py --trainingSet anotherstring
will end up setting train_path to the respective string value. It is up to the rest of your code to use that value.
The help parameter will show up in the help message, such as when you do
$ python myprog.py -h
The required parameter means that the parser will raise an error if you don't provide this argument, e.g.
$ python myprog.py

Related

Get arguments from ArgumentParser without calling parse_args

I have the following code and I want to use to extract the config parameter.
parser = argparse.ArgumentParser()
parser.add_argument(
"--config",
type=str,
default="src/config.yml",
dest="config"
)
My issue is that I cannot use parser.parse_args() (because I'm running the script from uvicorn and the parse_args is raising an error. Is there a way to retrieve the config parameter without the use of parse_args?
Other answers I've seen make use of parse_args.
If you want to parse an argument array that is not passed via sys.argv, for example one that you created, simply pass an array to the parse_args() function.
my_args = ["--config", "my_value"]
parsed_args = parser.parse_args(my_args)
print(parsed_args.config) # Prints "my_value"

Python argparse.ArgumentParser cannot differentiate between `--modes` and `--mode`

In this example script
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--modes', help="test", nargs='+', type=str)
args = parser.parse_args()
write_mode = args.modes
print(write_mode)
There is only one argument modes.
However, python test.py --modes sdfsf and python test.py --mode sdfsf give me the same output (['sdfsf']) which means the parser treats mode as modes.
Is this a bug of argparse?
This is an example of prefix matching, which is allowed by argparse by default.
Turn it off by using argparse.ArgumentParser(..., allow_abbrev=False).

How can I pass command line arguments contained in a file and retain the name of that file?

I have a script that consumes command line arguments and I would like to implement two argument-passing schemes, namely:
Typing the arguments out at the command line.
Storing the argument list in a file, and passing the name of this file to the program via the command line.
To that end I am passing the argument fromfile_prefix_chars to the ArgumentParser constructor.
script.py
from argparse import ArgumentParser
parser = ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)
args.txt
--foo
2
--bar
2
Sample use cases
$ python script.py --foo 3
Namespace(bar=1, filename=None, foo='3')
$ python script.py #args.txt --foo 3
Namespace(bar='2', filename=None, foo='3')
I was expecting that args.filename would retain the name of the file, but surprinsingly enough it has the value None instead. I am aware that I could get the file name from sys.argv through a bit of processing. Is there a cleaner way (ideally an argparse-based approach) to elicit the name of the arguments file?
Your script.py, plus the file. I have added the file name to the file itself.
args.txt
args.txt
--foo
2
--bar
2
testing:
1803:~/mypy$ python3 stack56811067.py --foo 3
Namespace(bar=1, filename=None, foo='3')
1553:~/mypy$ python3 stack56811067.py #args.txt --foo 3
Namespace(bar='2', filename='args.txt', foo='3')
From my testing, using fromfile_prefix_chars means that argparse will not actually pass the argument to your program. Instead, argparse sees the #args.txt, intercepts it, reads from it, and passes the arguments without #args.txt to your program. This is presumably because most people don't really need the filename, just need the arguments within, so argparse saves you the trouble of creating another argument to store something you don't need.
Unfortunately, all of the arguments are stored as local variables in argparse.py, so we cannot access them. I suppose that you could override some of argparse's functions. Keep in mind that this is a horrible, disgusting, hacky solution and I feel that parsing sys.argv is 100% better.
from argparse import ArgumentParser
# Most of the following is copied from argparse.py
def customReadArgs(self, arg_strings):
# expand arguments referencing files
new_arg_strings = []
for arg_string in arg_strings:
# for regular arguments, just add them back into the list
if not arg_string or arg_string[0] not in self.fromfile_prefix_chars:
new_arg_strings.append(arg_string)
# replace arguments referencing files with the file content
else:
try:
fn = arg_string[1:]
with open(fn) as args_file:
# What was changed: before was []
arg_strings = [fn]
for arg_line in args_file.read().splitlines():
for arg in self.convert_arg_line_to_args(arg_line):
arg_strings.append(arg)
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
except OSError:
err = _sys.exc_info()[1]
self.error(str(err))
# return the modified argument list
return new_arg_strings
ArgumentParser._read_args_from_files = customReadArgs
parser = ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)
Just for the record, here's a quick and dirty solution I came up with. It basically consists in creating a copy of parser and set its from_file_prefix_chars attribute to None:
import copy
parser_dupe = copy.copy(parser)
parser_dupe.fromfile_prefix_chars = None
args_raw = parser_dupe.parse_args()
if args_raw.filename:
args.filename = args_raw.filename[1:]

How to provide a python argparse.parser with arguments from inside the code, without command-line arguments?

I have a code that takes the command-line arguments into a parser and modifies some configuration settings. Something like this:
command:
python mycode.py --config-file "some_file.yaml" SOMETHING.subsetting_a 2 SOMETHING.subsetting_b 3
and then it does:
import argparse
parser = argparse.ArgumentParser(description="Some description here")
parser.add_argument(
"--config-file",
default="",
metavar="FILE",
help="path to config file",
type=str,
)
//some more 'add_argument' lines here
args = parser.parse_args()
But as I am using jupyter notebook, it would be easier to provide the arguments directly to the parser, as if they come from the command-line. How can I create a string containing the command (as mentioned above) and pass it to parser?
parse_args's first optional argument is the list of arguments to parse, the signature is:
ArgumentParser.parse_args(args=None, namespace=None)
It just takes args from sys.argv if you don't provide it.
So just call it as:
args = parser.parse_args(['mycode.py', '--config-file', "some_file.yaml", 'SOMETHING.subsetting_a', '2', 'SOMETHING.subsetting_a'])
(with the list containing whatever you like instead) and it will use it instead of sys.argv.
Note: As #ShadowRanger mentioned, there is no need to use sys.argv. See his response.
One way is to use sys.argv to mimic the command-line arguments:
import sys
sys.argv = [
"--config-file" , "some_file.yaml",
"SOMETHING.subsetting_a" , "2",
"SOMETHING.subsetting_b" , "3"]
args = parser.parse_args(sys.argv)
The content of args is something liek this:
> Namespace(config_file='some_file.yaml', opts=['SOMETHING.subsetting_a', '2', 'SOMETHING.subsetting_b', '3')
which is similar to the output of print(parser.parse_args()).

python argparse: 'Namespace' error

I'm trying to build a process with some parsing option, some mandatory and others optinal.
I have a problem with the following code:
bandlist=[1,2,3,4,5,6,7,8]
process=['rad', 'ref', 'radref']
sensors=['LC', 'LO', 'LE', 'LT']
series=['4', '5', '7', '8']
usage = "usage: %prog [options] "
parser = argparse.ArgumentParser(usage=usage)
parser.add_argument('-d', '--directory', dest='directory', action='store', type=str, \
help='complete path of landsat product folder: mydir/filename/')
parser.add_argument('-p', '--process', dest='operation', action='store', choices = process, \
help='process requested: radiance, reflectance, both', default='rad')
parser.add_argument('-l', '--series', dest='satellite', action='store', choices = series , \
help='Landsat series:4, 5, 7, 8')
parser.add_argument('-s', '--sensor', dest='sensor', action='store', choices = sensors, \
help='sensor acronymous, for example LO for Landsat OLI, or LE for Landsat ETM+, etc..', default=None)
parser.add_argument('-o', '--output', dest='output', type=str, \
help='Directory of output raster. \n \
Unless specified, output directory will be workDirectory/results/datafolder/. \n \
If specified, the output directory wil be mydirectory/results/filename/rad (and/or ref)/', default=None)
parser.add_argument('-x', action='store_true', dest='bool', help='activate iterative radiance and/or reflectance computation for all the bands', default=False)
parser.add_argument('-b', '--bands', dest='bands', choices = bandlist, type=int, \
help='bands to process', nargs='*', default=None)
(options, args) = parser.parse_args()
and there is the following error:
Traceback (most recent call last):
File "C:\Users\lbernardi\Desktop\extract\LandsatTMroutine_RadiometricCorrection_1.0.py", line 1210, in <module>
main()
File "C:\Users\lbernardi\Desktop\extract\LandsatTMroutine_RadiometricCorrection_1.0.py", line 784, in main
(options, args) = parser.parse_args()
TypeError: 'Namespace' object is not iterable
I don't understand what the error is about.
Thank for your help
parse_args doesn't return two items, it returns one.
args = parser.parse_args()
The error was issued by the interpreter while performing the (options, args) = ... assignment. The parser returned one object, a argparse.Namespace. But the assignment tries to split it into two items, e.g.
(options, args) = (args[0], args[1])
But the Namespace class definition does not implement a list or tuple like iteration. A custom Namespace class could, in theory, do so.
That's the technical detail behind the error. The practical issue is that argparse differs from optparse.
From the end of the argparse docs:
Replace (options, args) = parser.parse_args() with args = parser.parse_args() and add additional ArgumentParser.add_argument() calls for the positional arguments. Keep in mind that what was previously called options, now in argparse context is called args.
optparse processes all the flagged strings, and puts their values in an options object (I forget its exact nature). Strings that it can't process are returned as a list as the 2nd return value.
argparse expects you to define all arguments, both the flagged ones (called 'optionals') and unflagged ones (called 'positionals'). So values that in optparse would appear in the args list, appear under their own 'name/dest' in the argparse namespace.
There is another way of calling the argparse parser, parser.parse_known_args that behaves more like optparse. Strings it can't handle are returned in an extras list.
You mention that some of your arguments are required and some are not. At a first glance, your code makes everything 'optional'. That is, if you don't include the relevant flag in the commandline, that argument will get its default value.
One of your arguments uses nargs='*'. If that isn't specified the default will be None or [] (I forget the details).
You can also specify required=True parameter which makes an 'optional' required. Sorry about the confusing terminology. In that case, the parser will raise an error if you don't supply the flag in the commandline.
I didn't look much at your previous optparse question to see whether you expected an values in the args variable. In argparse usage those are 'positionals' and are required (unless their nargs makes them 'optional').
So while the simple fix is just use args = parser.parse_args(), I suspect there's more under the surface.
A style point:
parser.add_argument('-d', '--directory', dest='directory', action='store', type=str, \
help='complete path of t product folder: mydir/filename/')
can be simplified to
parser.add_argument('-d', '--directory', \
help='complete path of landsat product folder: mydir/filename/')
If not given dest is infered from the first long flag string. store is the default action value, and str is the default type.
In argparse, type is a function, one that converts the input string to something else. int and float are the most common alternatives. str works because it does nothing - returns the same string it was given. argparse actually uses an identify lambda function as the default type (e.g. lambda x:x).

Categories

Resources