While loop doesn't stop - python

I have this simple code in Python:
import sys
class Crawler(object):
def __init__(self, num_of_runs):
self.run_number = 1
self.num_of_runs = num_of_runs
def single_run(self):
#do stuff
pass
def run(self):
while self.run_number <= self.num_of_runs:
self.single_run()
print self.run_number
self.run_number += 1
if __name__ == "__main__":
num_of_runs = sys.argv[1]
crawler = Crawler(num_of_runs)
crawler.run()
Then, I run it this way:
python path/crawler.py 10
From my understanding, it should loop 10 times and stop, right? Why it doesn't?

num_of_runs = sys.argv[1]
num_of_runs is a string at that stage.
while self.run_number <= self.num_of_runs:
You are comparing a string and an int here.
A simple way to fix this is to convert it to an int
num_of_runs = int(sysargv[1])
Another way to deal with this is to use argparser.
import argparse
parser = argparse.ArgumentParser(description='The program does bla and bla')
parser.add_argument(
'my_int',
type=int,
help='an integer for the script'
)
args = parser.parse_args()
print args.my_int
print type(args.my_int)
Now if you execute the script like this:
./my_script.py 20
The output is:
20
Using argparser also gives you the -h option by default:
python my_script.py -h
usage: i.py [-h] my_int
The program does bla and bla
positional arguments:
my_int an integer for the script
optional arguments:
-h, --help show this help message and exit
For more information, have a look at the argparser documentation.
Note: The code I have used is from the argparser documentation, but has been slightly modified.

When accepting input from the command line, data is passed as a string. You need to convert this value to an int before you pass it to your Crawler class:
num_of_runs = int(sys.argv[1])
You can also utilize this to determine if the input is valid. If it doesn't convert to an int, it will throw an error.

Related

Getting python file to return different values based on the input arguments using argparse

Let's say I have a function f1()
def f1(input1, input2):
# does some magic
return p1, p2
if __name__ == "__main__":
parser = ArgumentParser(description='Help function description')
parser.add_argument('t1', help='Token')
parser.add_argument('t2', help='Another token')
args = parser.parse_args()
p1, p2 = f1(args.t1, args.t2)
Let's say this file is called street.py. I want to use the same file to return p1 when the argument arg1 is an input and p2 when argument arg2 is an input.
Outside the file :
variable1 = (python3 ../street.py t1 t2 -arg1)
variable2 = (python3 ../street.py t1 t2 -arg2)
I'm a bit confused over the argparse argument style from the documentation, what's the easiest way to do this?
You can solve your problem by using:
parser.add_argument('-arg1', '--argument1', action='store_true')
args = parser.parse_args()
this will store True in the variable args.argument1 if you call python3 ../street.py t1 t2 -arg1, so that you can later use a simple if/elif statement to separate the different cases, i.e.:
if args.argument1 is True:
print(p1)
TL;DR
Make the call in the terminal like this (linux)
variable=$(python3 streep.py t1 'foo' t2 'bar')
you should associate each param t1 and t2 with their respective values.
Also only return a single value from f1 which you will print to stdout
You should consider what you want the behaviour to be. If you want to use the output of f1 then be explicit in its arguments what is mandatory and what is an optional input.
Also consider what you want the output to be, since this is a script passing values will be through stdout you kinda only have the one line to print.
def f1(input1, input2):
# does some magic
return p1 or p2
Then simply print the result to stdout with python print() statement
An example based on your code could look like
from argparse import ArgumentParser
import sys
def f1(input1, input2):
# does some magic
return input1 or input2
if __name__ == "__main__":
parser = ArgumentParser(description='Help function description')
parser.add_argument('--t1', help='Token')
parser.add_argument('--t2', help='Another token')
args = parser.parse_args()
try:
result = f1(args.t1, args.t2)
print(result)
except Exception as e:
print(e, file=sys.stderr)
exit(1)
I can then use this script as
variable=$(python3 streep.py --t2 'bar')
With this file:
import argparse, sys
def f1(input1, input2):
# does some magic
print('inside f1')
return input1, input2
if __name__ == "__main__":
print(sys.argv)
parser = argparse.ArgumentParser(description='Help function description')
parser.add_argument('t1', help='Token')
parser.add_argument('t2', help='Another token')
args = parser.parse_args()
print(args)
p1, p2 = f1(args.t1, args.t2)
print(p1, p2)
Asking for help (in a shell window):
201:~/mypy$ python3 stack64684099.py -h foo bar
['stack64684099.py', '-h', 'foo', 'bar']
usage: stack64684099.py [-h] t1 t2
Help function description
positional arguments:
t1 Token
t2 Another token
optional arguments:
-h, --help show this help message and exit
running with 2 arguments as specified in the help:
1201:~/mypy$ python3 stack64684099.py foo bar
['stack64684099.py', 'foo', 'bar']
Namespace(t1='foo', t2='bar')
inside f1
foo bar
I added prints to show sys.argv the list of strings that argparse gets from the shell. And the namespace that parsing produced, and the results of running f1.
===
I don't know where you are doing:
variable1 = (python3 ../street.py t1 t2 -arg1)
variable2 = (python3 ../street.py t1 t2 -arg2)
There's nothing in your parser set up that looks for or pays attention to those '-arg1','-arg2' strings. And what they are supposed to do is not clear in your question.
I could redirect stdout to file:
1202:~/mypy$ python3 stack64684099.py foo bar > test.txt
1208:~/mypy$ cat test.txt
['stack64684099.py', 'foo', 'bar']
Namespace(t1='foo', t2='bar')
inside f1
foo bar
Omitting all but the last print give just the foo bar line.
The primary purpose of the parser is to read and understand/parse the user input. If you are happy with the results in the args namespace, the rest is up to your own code. As defined your arguments are simple strings, which you can use as is or test for certain values. You can also produce numbers, and with the correct action, boolean values. You can also define optionals, arguments that expect a flag string like --arg1.

How do I use/get the list element out of the namespace?

I'm trying to write a Python program that will take given list and return the average.
#program name: average.py, author: Leo
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-lst", nargs='+', type=int, required=True)
xyz = parser.parse_args()
def GetAvg(xyz):
total = 0
for i in xyz:
total = total + i
finalAvg = total / len(xyz)
return finalAvg
if __name__ == "__main__":
GetAvg(xyz)
When I run it in my cmd prompt I run it as
python average.py -lst 4 5 7 3 2
However, I always get the error message: 'Namespace' object is not iterable.
When I do a print(xyz) it returns "Namespace(lst=[4, 5, 7, 3, 2])".
So my question is:
How do I get this function to use the list within the namespace? -or- Do I use some other argparse function to accomplish this?
I apologize for any incorrect syntax or styling, first post on StackOverflow.
The parser returns the namespace with all arguments, you have to access the specific argument. Here is your program with a few commented changes:
import argparse
def get_avg(xyz): # use pep8-style names (get_avg instead of GetAvg)
total = sum(xyz) # use predefined Python functions
return total / len(xyz)
if __name__ == "__main__":
# put all of the main program here so that it is not executed
# if the function is called from elsewhere
parser = argparse.ArgumentParser()
parser.add_argument("-lst", nargs='+', type=int, required=True)
xyz = parser.parse_args().lst # access the needed argument
print(get_avg(xyz))

How to pass values to python function by executing instructions input as string?

I have a function:
def foo(a=0, b=0, c=0, val=0, someotherval=0):
print val + someotherval
This function is called inside a file bar.py. When I run bar.py from the console, I want to pass the arguments to the function as a string:
>>python bar.py "val=3"
So the function foo interprets it as:
foo(val=3)
I have attempted to use the exec command. In my bar.py file:
import sys
cmdlinearg = sys.argv[1] # capturing commandline argument
foo(exec(cmdlinearg))
But I get a syntax error.
I understand that I can just pass the argument values themselves, but with a function with many arguments, I do not want the end user to enter 0s for unneeded arguments:
>>python bar.py "0" "0" "0" "3"
Is there a way to accomplish this?
I would rather do this the proper way and use argparse.
Your command line interface would look such as:
bar.py --a 0 --b 0 --c 0 --val 0 --someotherval 0
And the code something along:
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('a', type=int, default=0)
...
args = parser.parse_args()
foo(a=args.a, b=args.b, c=args.c, var=args.val, someotherval=args.someotherval)
if __name__ == '__main__':
main()
If you have no concern for safety, you can just do
exec('foo(%s)' % sys.argv[1])
Or this:
def getdict(**vals):
return vals
kwargs = exec('getdict(%s)' % sys.argv[1])
foo(**kwargs)
However, if your concern is the user's ease of use, maybe you should take a look at argparse.
How about using the argparse for parsing the command line arguments?
Example -
import argparse
def foo(a=0, b=0, c=0, val=0, someotherval=0):
print(val + someotherval)
parser = argparse.ArgumentParser(description='Some Parser')
parser.add_argument('-a','--a',default=0,type=int,help="value for a")
parser.add_argument('-b','--b',default=0,type=int,help="value for a")
parser.add_argument('-c','--c',default=0,type=int,help="value for a")
parser.add_argument('-v','--val',default=0,type=int,help="value for a")
parser.add_argument('-s','--someotherval',default=0,type=int,help="value for a")
args = parser.parse_args()
foo(a=args.a,b=args.b,c=args.c,val=args.val,someotherval=args.someotherval)
Then you can call and get results like -
>python a.py
0
>python a.py --val 10
10
>python a.py --val 10 --someotherval 100
110
Take a look at this library:
http://click.pocoo.org
It may be useful in your case.
import click
#click.command()
#click.option('-a', default=0)
#click.option('-b', default=0)
#click.option('-c', default=0)
#click.option('--val', default=0)
#click.option('--someotherval', default=0)
def foo(a, b, c, val, someotherval):
print val + someotherval
if __name__ == '__main__':
foo()
However, you have to add use options as arguments, not strings:
>> python bar.py --val=3
You could just default the values for the other parameters if you know that they are never going to enter them.
foo(0,0,exec(cmdlinearg),0)

Python - Pass Arguments to Different Methods from Argparse

I'm writing a relatively simple Python script which supports a couple of different commands. The different commands support different options and I want to be able to pass the options parsed by argparse to the correct method for the specified command.
The usage string looks like so:
usage: script.py [-h]
{a, b, c}
...
script.py: error: too few arguments
I can easily call the appropriate method:
def a():
...
def b():
...
def c():
...
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.set_defaults(method = a)
...
arguments = parser.parse_args()
arguments.method()
However, I have to pass arguments to these methods and they all accept different sets of arguments.
Currently, I just pass the Namespace object returned by argparse, like so:
def a(arguments):
arg1 = getattr(arguments, 'arg1', None)
...
This seems a little awkward, and makes the methods a little harder to reuse as I have to pass arguments as a dict or namespace rather than as usual parameters.
I would like someway of defining the methods with parameters (as you would a normal function) and still be able to call them dynamically while passing appropriate parameters. Like so:
def a(arg1, arg2):
...
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.set_defaults(method = a)
...
arguments = parser.parse_args()
arguments.method() # <<<< Arguments passed here somehow
Any ideas?
I found quite a nice solution:
import argparse
def a(arg1, arg2, **kwargs):
print arg1
print arg2
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.set_defaults(method = a)
parser.add_argument('arg1', type = str)
parser.add_argument('arg2', type = str)
arguments = parser.parse_args()
arguments.method(**vars(arguments))
Of course there's a minor problem if the arguments of the method clash with the names of the arguments argparse uses, though I think this is preferable to passing the Namespace object around and using getattr.
You're probably trying to achieve the functionality that sub-commands provide:
http://docs.python.org/dev/library/argparse.html#sub-commands
Not sure how practical this is, but by using inspect you can leave out the extraneous **kwargs parameter on your functions, like so:
import argparse
import inspect
def sleep(seconds=0):
print "sleeping", seconds, "seconds"
def foo(a, b=2, **kwargs):
print "a=",a
print "b=",b
print "kwargs=",kwargs
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="subcommand")
parser_sleep = subparsers.add_parser('sleep')
parser_sleep.add_argument("seconds", type=int, default=0)
parser_sleep.set_defaults(func=sleep)
parser_foo = subparsers.add_parser('foo')
parser_foo.add_argument("-a", type=int, default=101)
parser_foo.add_argument("-b", type=int, default=201)
parser_foo.add_argument("--wacky", default=argparse.SUPPRESS)
parser_foo.set_defaults(func=foo)
args = parser.parse_args()
arg_spec = inspect.getargspec(args.func)
if arg_spec.keywords:
## convert args to a dictionary
args_for_func = vars(args)
else:
## get a subset of the dictionary containing just the arguments of func
args_for_func = {k:getattr(args, k) for k in arg_spec.args}
args.func(**args_for_func)
Examples:
$ python test.py sleep 23
sleeping 23 seconds
$ python test.py foo -a 333 -b 444
a= 333
b= 444
kwargs= {'func': <function foo at 0x10993dd70>}
$ python test.py foo -a 333 -b 444 --wacky "this is wacky"
a= 333
b= 444
kwargs= {'func': <function foo at 0x10a321d70>, 'wacky': 'this is wacky'}
Have fun!

Default sub-command, or handling no sub-command with argparse

How can I have a default sub-command, or handle the case where no sub-command is given using argparse?
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
a.parse_args()
Here I'd like a command to be selected, or the arguments to be handled based only on the next highest level of parser (in this case the top-level parser).
joiner#X:~/src> python3 default_subcommand.py
usage: default_subcommand.py [-h] {hi} ...
default_subcommand.py: error: too few arguments
On Python 3.2 (and 2.7) you will get that error, but not on 3.3 and 3.4 (no response). Therefore on 3.3/3.4 you could test for parsed_args to be an empty Namespace.
A more general solution is to add a method set_default_subparser() (taken from the ruamel.std.argparse package) and call that method just before parse_args():
import argparse
import sys
def set_default_subparser(self, name, args=None, positional_args=0):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_hi():
print('inside hi')
a = argparse.ArgumentParser()
b = a.add_subparsers()
sp = b.add_parser('hi')
sp.set_defaults(func=do_hi)
a.set_default_subparser('hi')
parsed_args = a.parse_args()
if hasattr(parsed_args, 'func'):
parsed_args.func()
This will work with 2.6 (if argparse is installed from PyPI), 2.7, 3.2, 3.3, 3.4. And allows you to do both
python3 default_subcommand.py
and
python3 default_subcommand.py hi
with the same effect.
Allowing to chose a new subparser for default, instead of one of the existing ones.
The first version of the code allows setting one of the previously-defined subparsers as a default one. The following modification allows adding a new default subparser, which could then be used to specifically process the case when no subparser was selected by user (different lines marked in the code)
def set_default_subparser(self, name, args=None, positional_args=0):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
existing_default = False # check if default parser previously defined
for arg in sys.argv[1:]:
if arg in ['-h', '--help']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if sp_name == name: # check existance of default parser
existing_default = True
if not subparser_found:
# If the default subparser is not among the existing ones,
# create a new parser.
# As this is called just before 'parse_args', the default
# parser created here will not pollute the help output.
if not existing_default:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
x.add_parser(name)
break # this works OK, but should I check further?
# insert default in last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
a = argparse.ArgumentParser()
b = a.add_subparsers(dest ='cmd')
sp = b.add_parser('hi')
sp2 = b.add_parser('hai')
a.set_default_subparser('hey')
parsed_args = a.parse_args()
print(parsed_args)
The "default" option will still not show up in the help:
python test_parser.py -h
usage: test_parser.py [-h] {hi,hai} ...
positional arguments:
{hi,hai}
optional arguments:
-h, --help show this help message and exit
However, it is now possible to differentiate between and separately handle calling one of the provided subparsers, and calling the default subparser when no argument was provided:
$ python test_parser.py hi
Namespace(cmd='hi')
$ python test_parser.py
Namespace(cmd='hey')
It seems I've stumbled on the solution eventually myself.
If the command is optional, then this makes the command an option. In my original parser configuration, I had a package command that could take a range of possible steps, or it would perform all steps if none was given. This makes the step a choice:
parser = argparse.ArgumentParser()
command_parser = subparsers.add_parser('command')
command_parser.add_argument('--step', choices=['prepare', 'configure', 'compile', 'stage', 'package'])
...other command parsers
parsed_args = parser.parse_args()
if parsed_args.step is None:
do all the steps...
Here's a nicer way of adding a set_default_subparser method:
class DefaultSubcommandArgParse(argparse.ArgumentParser):
__default_subparser = None
def set_default_subparser(self, name):
self.__default_subparser = name
def _parse_known_args(self, arg_strings, *args, **kwargs):
in_args = set(arg_strings)
d_sp = self.__default_subparser
if d_sp is not None and not {'-h', '--help'}.intersection(in_args):
for x in self._subparsers._actions:
subparser_found = (
isinstance(x, argparse._SubParsersAction) and
in_args.intersection(x._name_parser_map.keys())
)
if subparser_found:
break
else:
# insert default in first position, this implies no
# global options without a sub_parsers specified
arg_strings = [d_sp] + arg_strings
return super(DefaultSubcommandArgParse, self)._parse_known_args(
arg_strings, *args, **kwargs
)
Maybe what you're looking for is the dest argument of add_subparsers:
(Warning: works in Python 3.4, but not in 2.7)
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='cmd')
parser_hi = subparsers.add_parser('hi')
parser.parse_args([]) # Namespace(cmd=None)
Now you can just use the value of cmd:
if cmd in [None, 'hi']:
print('command "hi"')
You can duplicate the default action of a specific subparser on the main parser, effectively making it the default.
import argparse
p = argparse.ArgumentParser()
sp = p.add_subparsers()
a = sp.add_parser('a')
a.set_defaults(func=do_a)
b = sp.add_parser('b')
b.set_defaults(func=do_b)
p.set_defaults(func=do_b)
args = p.parse_args()
if args.func:
args.func()
else:
parser.print_help()
Does not work with add_subparsers(required=True), which is why the if args.func is down there.
In my case I found it easiest to explicitly provide the subcommand name to parse_args() when argv was empty.
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='commands')
runParser = subparsers.add_parser('run', help='[DEFAULT ACTION]')
altParser = subparsers.add_parser('alt', help='Alternate command')
altParser.add_argument('alt_val', type=str, help='Value required for alt command.')
# Here's my shortcut: If `argv` only contains the script name,
# manually inject our "default" command.
args = parser.parse_args(['run'] if len(sys.argv) == 1 else None)
print args
Example runs:
$ ./test.py
Namespace()
$ ./test.py alt blah
Namespace(alt_val='blah')
$ ./test.py blah
usage: test.py [-h] {run,alt} ...
test.py: error: invalid choice: 'blah' (choose from 'run', 'alt')
In python 2.7, you can override the error behaviour in a subclass (a shame there isn't a nicer way to differentiate the error):
import argparse
class ExceptionArgParser(argparse.ArgumentParser):
def error(self, message):
if "invalid choice" in message:
# throw exception (of your choice) to catch
raise RuntimeError(message)
else:
# restore normal behaviour
super(ExceptionArgParser, self).error(message)
parser = ExceptionArgParser()
subparsers = parser.add_subparsers(title='Modes', dest='mode')
default_parser = subparsers.add_parser('default')
default_parser.add_argument('a', nargs="+")
other_parser = subparsers.add_parser('other')
other_parser.add_argument('b', nargs="+")
try:
args = parser.parse_args()
except RuntimeError:
args = default_parser.parse_args()
# force the mode into namespace
setattr(args, 'mode', 'default')
print args
Here's another solution using a helper function to build a list of known subcommands:
import argparse
def parse_args(argv):
parser = argparse.ArgumentParser()
commands = []
subparsers = parser.add_subparsers(dest='command')
def add_command(name, *args, **kwargs):
commands.append(name)
return subparsers.add_parser(name, *args, **kwargs)
hi = add_command("hi")
hi.add_argument('--name')
add_command("hola")
# check for default command
if not argv or argv[0] not in commands:
argv.insert(0, "hi")
return parser.parse_args(argv)
assert parse_args([]).command == 'hi'
assert parse_args(['hi']).command == 'hi'
assert parse_args(['hi', '--name', 'John']).command == 'hi'
assert parse_args(['hi', '--name', 'John']).name == 'John'
assert parse_args(['--name', 'John']).command == 'hi'
assert parse_args(['hola']).command == 'hola'
You can add an argument with a default value that will be used when nothing is set I believe.
See this: http://docs.python.org/dev/library/argparse.html#default
Edit:
Sorry, I read your question a bit fast.
I do not think you would have a direct way of doing what you want via argparse. But you could check the length of sys.argv and if its length is 1 (only script name) then you could manually pass the default parameters for parsing, doing something like this:
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser('hi')
if len(sys.argv) == 1:
a.parse_args(['hi'])
else:
a.parse_args()
I think that should do what you want, but I agree it would be nice to have this out of the box.
For later reference:
...
b = a.add_subparsers(dest='cmd')
b.set_defaults(cmd='hey') # <-- this makes hey as default
b.add_parser('hi')
so, these two will be same:
python main.py hey
python main.py

Categories

Resources