call argparse print_help from function - python

i would like to call argparse print_help() from inside a function, but parser is in another function. Let's say:
import argparse
def f():
parser.print_help()
def a():
f()
def _read_args():
parser = argparse.ArgumentParser(description="my description")
parser.add_argument('-c', action='store_true')
return parser
def main():
parser = _read_args()
args = parser.parse_args()
a()
# MAIN
if __name__ == '__main__':
main()
i thought 2 solutions:
make parser global
read args in main ( not main() )
the 1 i don't really like global variables, if possible i prefer not use it
the 2 present problems if importing the module
which is the best way to achieve this?
thanks to all

If you want access to a variable inside a function, pass it as an argument.
def f(parser):
parser.print_help()
def a(parser):
f(parser)
...
def main():
parser = _read_args()
args = parser.parse_args()
a(parser)

Related

How to parse arguments to a function in Python?

I've been trying to understand tutorials on how to parse arguments in Python using argparse. Is this how I would pass a command line input so I can run a function?
import argparse
parser = argparse.ArgumentParser(description='A test')
parser.add_argument("--a", default=1, help="Test variable")
args = parser.parse_args()
def foo():
command_line_argument = args.a
bar = 2*args.a
print(bar)
return
if "__name__" == "__main__"
try:
while True:
foo()
except KeyboardInterrupt:
print('User has exited the program')
That while True looks odd to me -- are you asking the reader to keep submitting inputs until they CTRL+C ? Because if so, argparse is the wrong thing to use: see Getting user input
If you intend a single argument then I'd move the parser stuff inside main, which is what gets executed when the script is run as a program as opposed to being imported.
Also, I'd pass a parameter to foo rather than the args block.
Lastly, I guess you're expecting to receive a number so you need type=int or similar.
import argparse
def foo(a):
bar = 2*a
print(bar)
return
if __name__ == "__main__":
try:
# set it up
parser = argparse.ArgumentParser(description='A test')
parser.add_argument("--a", type=int, default=1, help="Test variable")
# get it
args = parser.parse_args()
a = args.a
# use it
foo(a)
except KeyboardInterrupt:
print('User has exited the program')
So:
$ python foo.py --a 1
2
below is in working state
import argparse
parser = argparse.ArgumentParser(description='A test')
parser.add_argument("--a", default=1, help="Test variable", type=int)
args = parser.parse_args()
def foo():
command_line_argument = args.a
bar = 2*args.a
print(bar)
return
if __name__ == "__main__":
try:
while True:
foo()
except KeyboardInterrupt:
print('User has exited the program')
if you run python your-filename.py --a=2 it will print 4 until you stop the execution.

Passing command line arguments to a function that calls a decorated function with decorator arguments

This example is contrived but represents a real life situation:
I have a python script that takes command line arguments.
main() will parse the arguments, and pass them on to an intermediate function (caller_func in the code example)
The intermediate function will then call a decorated function (fib() in the example) that is decorated with lru_cache from functools, and the maxsize of the cache is an argument to be accepted from command line and passed via the intermediate function.
How do I do this?
import argparse
from functools import lru_cache
def main():
# boilerplate for parsing command line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--cache_size", default="10")
parser.add_argument("--fibo_num", default="20")
args = parser.parse_args()
cache_size = int(args.cache_size)
fibo_num = int(args.fibo_num)
caller_func(cache_size, fibo_num)
#Intermediate function that is supposed to call decorated function
def caller_func(cache_size, fib_num):
print(fib(fib_num))
#function decorated by LRU cache
#lru_cache(maxsize=cache_size)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
if __name__ == "__main__":
main()
run as
python3 example.py --cache_size 5 --fibo_num 30
throws
NameError: name 'cache_size' is not defined
I tried making cache_size a global variable, but it didn't work, and I don't want globals anyway.
You don't have to use a decorator with decorator syntax. You can wait to "decorate" fib until after you have the desired cache size. For example,
import argparse
from functools import lru_cache
def main():
global fib
# boilerplate for parsing command line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--cache_size", default="10")
parser.add_argument("--fibo_num", default="20")
args = parser.parse_args()
cache_size = int(args.cache_size)
fibo_num = int(args.fibo_num)
fib = lru_cache(maxsize=cache_size)(fib)
caller_func(fibo_num)
#Intermediate function that is supposed to call decorated function
def caller_func(fib_num):
print(fib(fib_num))
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
if __name__ == "__main__":
main()

Using Argparse within a Class __init__

Is it possible to use Argparse from within a class and if so, how would you get the parameters to the parser? Is there a better way of going about this? Here is my code so far:
class Game:
def __init__(self):
parser = argparse.ArgumentParser()
parser.add_argument("num_players", help="Number of players", type=int)
...
args = parser.parse_args()
if __name__ == '__main__':
g = Game()
Also Is there a way of supplying optional arguments such as --verbose?
It is highly unlikely to be the best design.
The Game class should probably not care how it was initialized, and it should probably know nothing about command line arguments. You will be better off parsing the CLI args outside, then pass the arguments to Game.__init__ or at least to a dedicated Game.from_cli_args classmethod.
import argparse
class Game:
def __init__(self, num_players):
print('Game got {} as num_players'.format(num_players))
self.num_players = num_players
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("num_players", help="Number of players", type=int)
...
args = parser.parse_args()
g = Game(args.num_players)
Then executing:
$ python main.py 2
Game got 2 as num_players
If you use -- as a prefix the argument will need to be passed explicitly:
parser.add_argument("--num_players", help="Number of players", type=int)
Then
$ python main.py --num_players 2
Game got 2 as num_players
yeah, you can use argparse inside class constructor.just create parser object and pass all arguments to that object, once you initialise class you can access all arguments using that parser object.
class test:
def __init__(self):
self.parser = argparse.ArgumentParser(description='Process some integers.')
self.parser.add_argument('integers', metavar='N', type=int, nargs='+',help='an integer for the accumulator')
def test123(self):
args = self.parser.parse_args()
# prints argument
print(args)
if __name__ == '__main__':
x = test()
x.test123()
output: Namespace(integers=[1, 2, 3, 4])

How can I remove CLI arguments using argparse so unittest will accept arg list

I'd like to pass my own arguments into files that are setup for unittest. So calling it from the command line like this should work:
python Test.py --c keith.ini SomeTests.test_one
Currently I'm running into two issues.
1) Arg parse doesn't allow unknown arguments
usage: Test.py [-h] [--c CONFILE]
Test.py: error: unrecognized arguments: SomeTests.test_one
2) Unit test doesn't allow unknown arguments. So --c fileName is not accepted by unittest and returns:
AttributeError: 'module' object has no attribute 'keith'
So the idea is to collect my arguments and remove them before calling unittest runner.
import unittest
import argparse
myArgs = None
def getArgs( allArgs ):
parser = argparse.ArgumentParser( )
parser.add_argument('--c', dest='conFile', type=str, default=None, help='Config file')
args = parser.parse_args()
if ( args.conFile == None ):
parser.print_help()
return args
class SomeTests(unittest.TestCase):
def test_one(self):
theTest( 'keith' )
def test_two(self):
otherTest( 'keith' )
if __name__ == '__main__':
myArgs = getArgs( sys.argv )
print 'Config File: ' + myArgs.conFile
unittest.main( argv=sys.argv, testRunner = unittest.TextTestRunner(verbosity=2))
Interesting I just found parse_known_args() so I changed the parse line to:
args = parser.parse_known_args(['--c']).
I thought this would solve my issue and give me something to pass to unittest. Unfortunately I get:
Test.py: error: argument --c: expected one argument.
Shouldn't this work?
OK took a bit of effort but figured it out. This is totally possible. The documentation for argparse is not correct. The function parse_known_args() should not include a list of known arguments. Also argparse removes arg[0] which is important to return so other commands see a valid argument list. I'd consider this removal a bug. I have included the final example code.
import unittest
import argparse
import sys
myArgs = None
def getArgs( allArgs ):
parser = argparse.ArgumentParser( )
parser.add_argument('--c', dest='conFile', type=str, default=None, help='Configuration file. (Required)')
args, addArgs = parser.parse_known_args( )
if ( args.conFile == None ):
parser.print_help()
sys.exit(2)
# argparse strips argv[0] so prepend it
return args, [ sys.argv[0]] + addArgs
def verify( expected, actual ):
assert expected == actual, 'Test Failed: '
# Reusable Test
def theTest( exp ):
print 'myargs: ' + str( myArgs )
verify( exp, 'keith' )
def otherTest( exp ):
theTest( exp )
class SomeTests(unittest.TestCase):
def test_one(self):
theTest( 'keith' )
def test_two(self):
otherTest( 'keith2' )
if __name__ == '__main__':
myArgs, addArgs = getArgs( sys.argv )
unittest.main( argv=addArgs, testRunner = unittest.TextTestRunner(verbosity=2))
Once you save this to a file you can call it like the examples below and it will all work.
python Test.py # Requires config file
python Test.py --c keith.ini # Runs all tests
python Test.py --c keith.ini SomeTests # Runs Class
python Test.py --c keith.ini SomeTests.test_one # Runs test
HTH, Enjoy

Python: argument parser - multiple arguments without "--" allowed, not required

I am struggling to write a simple python script (a pseudo-git), which will allow me to call it from commandline/shell using comand like this:
$ python script.py init
I found some solutions online, which enabled me to do so, but there is little issue though. I want "add" function to accept other arguments as well (strings in general, which will represent files' names).
I have found a workaround, but it doesn't look nice. Is there a way to refactor the code, so that "add" will accept also other arguments and let me access them later on? Important thing: I don't want them to be added with "--", I'd rather separate them by simply adding space between two arguments.
I found "nargs='+' option in add_argument, but I don't know how to redirect the argument to call a proper function.
Here is my code I wrote so far:
import argparse
import sys
def init():
print("init method call")
def add():
if(len(sys.argv)>2):
print("valid add method call")
else:
print("invalid call")
def commit():
print("commit method call")
def status():
print("status method call")
def test():
print("test method call")
FUNCTION_MAP = {'init' : init,
'status' : status,
'commit': commit}
if __name__ == '__main__':
if(len(sys.argv)>1 and sys.argv[1] == "add"):
add()
else:
parser = argparse.ArgumentParser()
parser.add_argument('command', choices=FUNCTION_MAP.keys())
args = parser.parse_args()
func = FUNCTION_MAP[args.command]
func()
Here are 2 options - take an extra positional, and use or ignore the values. Or use subparsers.
import argparse
def add(args):
print("add:", args)
def status(args):
values = getattr(args,'values',None)
if values and len(values)>0:
# error message and method of your choice
print('oops - got values', args)
print("status method call")
FUNCTION_MAP = {'add' : add,
'status' : status}
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('command', choices=FUNCTION_MAP.keys())
parser.add_argument('values', nargs='*')
args = parser.parse_args()
func = FUNCTION_MAP[args.command]
func(args)
print('\nWIth subparsers')
parser = argparse.ArgumentParser()
sp = parser.add_subparsers(dest='command')
spp = sp.add_parser('add')
spp.add_argument('values', nargs='*')
spp = sp.add_parser('status')
args = parser.parse_args()
func = FUNCTION_MAP[args.command]
func(args)
Sample runs
1906:~/mypy$ python3 stack49691897.py add 1 2 3
add: Namespace(command='add', values=['1', '2', '3'])
WIth subparsers
add: Namespace(command='add', values=['1', '2', '3'])
1907:~/mypy$ python3 stack49691897.py status
status method call
WIth subparsers
status method call
1907:~/mypy$ python3 stack49691897.py status 1 2 3
oops - got values Namespace(command='status', values=['1', '2', '3'])
status method call
WIth subparsers
usage: stack49691897.py [-h] {add,status} ...
stack49691897.py: error: unrecognized arguments: 1 2 3
The argparse docs also demonstrates a way of using subparsers and defaults that effectively implements your FUNCTION_MAP.

Categories