does parser always take in arguments as list? - python

Python Version: Python 3.5.1
Django Version: Django 1.10.2
I am trying to write my own django custom command and I noticed that to take in an argument, it always ends up as a list.
See https://docs.python.org/3/library/argparse.html
Notice that the arguments for integers is a list of integer.
I wanted to have an argument that takes in a relative path or absolute path to a directory written in obviously str format.
My question is:
is it even possible to only accept the argument as a single str object for the parser object?
if it's possible, what do I need to change?
My current code is
def add_arguments(self, parser):
parser.add_argument('path', nargs='+', type=str)
# Named (optional) arguments
parser.add_argument(
'--whiteware',
action='store_true',
dest='whiteware',
default=True,
help='Affects whiteware variants only',
)
def handle(self, *args, **options):
directory_in_str = options['path']
print(directory_in_str)

Your issue is with the way you are creating the command line argument path.
From the documentation,
nargs - The number of command-line arguments that should be consumed.
and nargs='+' implies one or more space separated arguments, which would be casted into a list by argparse.
Now, if you are expecting a string, you can just do:
parser.add_argument('path', type=str) #type is str by default, no need to specify this explicitly.
Note that nargs is extremely useful when you want to restrict the choice types, etc.
For example:
parser.add_argument('path', nargs='+', choices=['a', 'b', 'c'])
This way, you can provide a bunch of options which would be available as a list for consumption.
Or even:
parser.add_argument('path', choices=['a', 'b', 'c'])
If you want a single option as a string.
You can read more on argparse options here in the documentation

Related

'Namespace' object is not iterable

Attempting to pass an undetermined amount of integers using argparse. When I input: py main.py 3 2
%%writefile main.py
import sorthelper
import argparse
integers = 0
#top-level parser creation
parser = argparse.ArgumentParser("For sorting integers")
nargs = '+' #-> gathers cmd line arguments into a list
args = parser.add_argument('-f', metavar='N', type=int, nargs='+', help='yada yada yada')
args = parser.parse_args()
print(sorthelper.sortNumbers(args))
%%writefile sorthelper.py
def sortNumbers(args):
sorted(args)
Error Namespace Argument is not iterable
I think is is because I am passing an argument that is not of the correct type. After reading through all the documentation I could find I cannot figure out how to make this work. I want the program to sort the numbers I am passing.
parser.parse_args() returns a Namespace object, which is an object whose attributes represent the flags that were parsed. It is not iterable.
It seems like you want to get the command-line arguments given after -f, in which case you would take that particular flag out of the Namespace object:
print(sorthelper.sortNumbers(args.f))
Also, your code as you currently have it will print None, because sortNumbers() doesn't return anything. The built-in sorted() function does not sort in place (though list.sort() does, if you want to use that), so you have to actually do
def sortNumbers(args):
return sorted(args)

why parseargs stores the argument inside a list

I have an argparse that is given a string:
def f():
return 'dummy2'
p = argparse.ArgumentParser()
p.add_argument('--a', nargs=1, type=str)
p.add_argument('--b', nargs='?', const=f(), default=f())
p.parse_args('--a dummy'.split())
The parser namespace is Namespace(a=['dummy'], b='dummy2').
How can I make the argument for a be stored as a string and not as a list of strings?
It's simple, just skip the argument for nargs. Try this:
p = argparse.ArgumentParser()
p.add_argument('--a', type=str)
p.add_argument('--b', nargs='?', const=f(), default=f())
I believe this is what you expected:
p.parse_args('--a dummy'.split())
=> Namespace(a='dummy', b='dummy2')
Quoting the docs:
ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The nargs keyword argument associates a different number of command-line arguments with a single action. The supported values are:
N (an integer). N arguments from the command line will be gathered together into a list ... Note that nargs=1 produces a list of one item. This is different from the default, in which the item is produced by itself.

How can I make argparse apply type conversion to default value of positional arguments?

argparse applies type conversion to arguments of options, and to the default values of these if the default values are strings. However, it seems it doesn't do so for positional arguments:
import argparse as ap
p = ap.ArgumentParser()
p.add_argument('file', nargs='*',
metavar='FILE',
default='-',
type=ap.FileType(),
help='Input files. - is stdin. Default: %(default)s')
print(p.parse_args([]))
# Namespace(file='-')
print(p.parse_args(['-']))
# Namespace(file=[<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>])
(Ideone)
If I change file to --file so that these are option arguments, the default value is converted as expected.
Do I have any options better than explicitly passing argparse.FileType()('-') as the default?
The values put into sys.argv are always str objects, because the underlying operating system construct is an array is C character pointers. You need type to convert them to some other Python type.
The value of default, however, is under no such restriction. You can use a value of any type you like for that keyword argument.
p.add_argument('file',
nargs='*',
metavar='FILE',
default=[sys.stdin],
type=ap.FileType(),
help='Input files. - is stdin. Default: standard input')
I modified the help; getting the value of repr(sys.stdin) isn't particular useful, and the user should not be confused by describing the default value rather than specifying an exact Python object.

Why is there a difference when calling argparse.parse_args() or .parse_args(sys.argv)

I have created the following argument parser in my python code.
parser = argparse.ArgumentParser()
parser.add_argument('projectPath')
parser.add_argument('-project')
parser.add_argument('-release')
parser.add_argument('--test', default=False, action='store_true')
args = parser.parse_args()
and I'm executing my program the following way.
myProgram.py /path/to/file -project super --test
it works fine if I use the sysntax above with
args = parser.parse_args()
However if I take and use the sys.argv as input
args = parser.parse_args(sys.argv)
The parser is suddenly picky about the order of the arguments and I get the unrecognized argument error.
usage: fbu.py [-h] [-project PROJECT] [-release RELEASE] [--test] projectPath
fbu.py: error: unrecognized arguments: /path/to/file
As I can see from the error and also using the -h argument. The path argument must be last and the error makes sense in the last example.
But why does it not care about the order in the first example ?
EDIT: I'm using python version 3.4.3
sys.argv contains the script name as the first item, i.e. myProgram.py. That argument takes the spot of projectPath. Now there's one additional positional argument /path/to/file, which can't be matched to any arguments, hence the error.
Calling parse_args without arguments ArgumentParser is clever enough to omit the script name from being parsed. But when explicitly passing an array of arguments, it can't do that and will parse everything.
As you can see from looking at the source code for parse_known_args (which is called by parse_args):
if args is None:
# args default to the system args
args = _sys.argv[1:]
When you don't provide the arguments explicitly, Python removes the first item from .argv (which is the name of the script). If you pass the arguments manually, you must do this yourself:
parser.parse_args(sys.argv[1:])
This isn't explicitly covered in the documentation, but note that this section doesn't include a script name when calling parse_args manually:
Beyond sys.argv
Sometimes it may be useful to have an ArgumentParser parse arguments
other than those of sys.argv. This can be accomplished by passing a
list of strings to parse_args(). This is useful for testing at the
interactive prompt:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument(
... 'integers', metavar='int', type=int, choices=xrange(10),
... nargs='+', help='an integer in the range 0..9')
>>> parser.add_argument(
... '--sum', dest='accumulate', action='store_const', const=sum,
... default=max, help='sum the integers (default: find the max)')
>>> parser.parse_args(['1', '2', '3', '4'])
Namespace(accumulate=<built-in function max>, integers=[1, 2, 3, 4])
>>> parser.parse_args('1 2 3 4 --sum'.split())
Namespace(accumulate=<built-in function sum>, integers=[1, 2, 3, 4])
The advantage of passing the arguments manually is that it makes it easier to test the parsing functionality, as you can pass in a list of appropriate arguments rather than trying to patch sys.argv.

Python argparse: single-valued argument but allow specified multiple times on command line

In python argparse, is it possible to declare an argument which is just single-valued, instead a list, but allows being specified multiple times, the latest one overrides the earlier ones?
The use case is, I am writing a Python program that reads command line argument from a ~/.xxxrc file, where .xxxrc file has an command line argument per line. I want to allow user override the value in ~/.xxxrc file through command line. My plan is to implicitly adds an #~/.xxxrc to the beginning of argv before passing it to argparse, so that argparse will reads in the file. Now the problem turns into my question above.
Is it possible to achieve this effect? If not, is there any alternative solution to my use case.
The argparse module does that by default. Take a look at this example, where we specify the same flag twice, and the last one wins:
import argparse
parser = argparse.ArgumentParser(description='example')
parser.add_argument('-a', '--add')
options = parser.parse_args(['-a', 'foo', '-a', 'bar'])
print 'add = {}'.format(options.add) # output: add = bar
Yes, you can just create a custom action with an nargs='*' or nargs='+'.
Something like this should work:
class GetLastAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if values:
setattr(namespace, self.dest, values[-1])
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', action=GetLastAction)

Categories

Resources