'Namespace' object is not iterable - python

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)

Related

How to pass a dictionary in argparse and then use it in a class method in python [duplicate]

This question already has answers here:
type=dict in argparse.add_argument()
(13 answers)
Closed 9 years ago.
I'm trying to accept an argument of type=dict with argparse but no matter the input it gives an error of invalid dict value.
#!/usr/bin/env python
import argparse
MYDICT = {'key': 'value'}
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mydict", action="store",
required=False, type=dict,
default=MYDICT)
args = parser.parse_args()
print args.mydict
This is what happens when I try and pass a dictionary to the script
./argp.py -m "{'key1': 'value1'}"
usage: argp.py [-h] [-m MYDICT]
argp.py: error: argument -m/--mydict: invalid dict value: "{'key1': 'value1'}"
Looking at the documents I would think that this would be possible.
http://docs.python.org/dev/library/argparse.html
“Any object that supports the in operator can be passed as the choices value, so dict objects, set objects, custom containers, etc. are all supported.”
I do not think it is possible to pass a dictionary as an argument in the command line because there doesn't exist a conversion function from string to dict (EDIT: A hack is possible which gives similar behaviour, see below). What you are essentially telling python to do is:
dict("{'key1': 'value1'}")
Which if you try it out in the python console, does not work.
What the phrase:
"Any object that supports the in operator can be passed as the choices value, so dict objects, set objects, custom containers, etc. are all supported."
refers to is the choices argument that can be passed with the add_argument function - not to the type argument.
Your best bet is to probably accept your argument as a string and then convert it using the json capabilities of python:
parser.add_argument('-m', '--my-dict', type=str)
args = parser.parse_args()
import json
my_dictionary = json.loads(args.my_dict)
You can then pass a dictionary in the form of a string. You can try the json encoder/decoder out for yourself in the python console to see how it works:
>>>json.loads('{"value1":"key1"}')
{u'value1': u'key1'}
EDIT: hpaulj has pointed out to me that you can "hack" the type parameter by passing it json.loads which allows you to pass JSON that is similar looking to a dictionary.
import json
parser.add_argument('-d', '--my-dict', type=json.loads)
args = parse.parse_args()
mydict = args.my_dict # Will return a dictionary
NOTE: The input format you pass is not the same as python dictionary but is probably similar enough for your use case.
The reason this works is actually quite interesting because internally argparse will just use the parameter value as a function to convert the argument. i.e. if type=int then it will use int(arg) or if type=json.loads then json.loads(arg)
This also means that you can pass any function which takes a single parameter in as the argument to type and perform custom conversions if you need to :)

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.

does parser always take in arguments as list?

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

Using parser_args values to feed a for loop (Python 3.6)

I am trying to create a script that takes multiple command line arguments of the same type, and then feeds them into a for loop for processing, the below is what I have in my code:
import argparse
parser = argparse.ArgumentParser(description='XYZ')
parser.add_argument('[values]', nargs='+', help='A list of values')
u = parser.parse_args(['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'])
for values in u:
... # do a bunch of stuff for each "value"
I understand that the values stored in parse_args are a NAMESPACE, hence the error below when I run my code:
TypeError: 'Namespace' object is not iterable
Is there any way to turn them into a list object? so that I can pass them to my for loop, or is there any other approach I should be using? Just getting started with python, so apologies in advance if this is a noob question
You need to iterate u.values (the argument name you specified in add_argument call) not u itself; To do that you need to rename [values] to values:
import argparse
parser = argparse.ArgumentParser(description='XYZ')
parser.add_argument('values', nargs='+', help='A list of values')
# ^^^^^^^^
u = parser.parse_args(['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'])
for value in u.values:
# ^^^^^^^^
# do a bunch of stuff for each "value"
The values from parsing are in a simple of object of class argparse.namespace (which can be customized - see the docs).
Usually you access those values by attribute name, e.g. args.foo. For unusual names you many have to use getattr(args, '[values]').
In the simple case argument values are a single string or number. With nargs like '*' or '+', the values will be in a list. With append action type you may even get lists within lists.
I encourage users to print the args namespace.
In [2]: parser = argparse.ArgumentParser(description='XYZ')
...: parser.add_argument('[values]', nargs='+', help='A list of values')....
In [3]: args =parser.parse_args(['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'])
In [4]: print(args)
Namespace([values]=['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'])
With your 'dest' I have to use the getattr to fetch the list of strings from args.
In [5]: getattr(args,'[values]')
Out[5]: ['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q']
args is a simple object, so you can add attributes or modify existing ones. e.g.
In [6]: args.values = ['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q']
In [7]: print(args)
Namespace([values]=['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'],
values=['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q'])
If you are more familiar with dictionaries, you can use vars():
In [9]: vars(args)['values']
Out[9]: ['3crlV5QaiuUjRz5kWze', 'F9Xw0rggHZ_PLs62q']
Play around with this sort of thing an interactive Python session.

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.

Categories

Resources