This question already has answers here:
Calling functions by array index in Python
(5 answers)
Closed 1 year ago.
I am new to python and I am exploring python I have many different functions in 1 file want to expose those functions to client.
This is my app.py file
import sys
def get_data(e_id, t_id):
#some code
def get_2_data(e_id, t_id)
#some code
def get_3_data(t_id)
#some code
if __name__ == '__main__':
get_data(sys.argv[1], sys.argv[2])
Here I want to get specific function data. Currently I am running python app.py 1234 1.
The function which is defined under main gets called.
But I want get_3_data() data. How to call the particular function or someone wants to fetch get_2_data(). How to expose those functions. Any suggestion. I dont want any HTTP call or API. I want to call by method name.
The clean way to do this is by using the module argparse.
Here is how to do this:
import argparse
def get_data(args):
e_id = args.e_id
t_id = args.t_id
print(f'Function get_data. e_id: {e_id}. t_id: {t_id}')
def get_data_2(args):
e_id = args.e_id
t_id = args.t_id
print(f'Function get_data_2. e_id: {e_id}. t_id: {t_id}')
def get_data_3(args):
t_id = args.t_id
print(f'Function get_data_3. t_id: {t_id}')
if __name__ == '__main__':
# Create the arguments parser
argparser = argparse.ArgumentParser()
# Create the subparsers
subparsers = argparser.add_subparsers()
# Add a parser for the first function
get_data_parser = subparsers.add_parser('get_data')
# Set the function name
get_data_parser.set_defaults(func=get_data)
# Add its arguments
get_data_parser.add_argument('e_id')
get_data_parser.add_argument('t_id')
# Add a parser for the second function
get_data_2_parser = subparsers.add_parser('get_data_2')
# Set the function name
get_data_2_parser.set_defaults(func=get_data_2)
# Add its arguments
get_data_2_parser.add_argument('e_id')
get_data_2_parser.add_argument('t_id')
# Add a parser for the third function
get_data_3_parser = subparsers.add_parser('get_data_3')
# Set the function name
get_data_3_parser.set_defaults(func=get_data_3)
# Add its arguments
get_data_3_parser.add_argument('t_id')
# Get the arguments from the comand line
args = argparser.parse_args()
# Call the selected function
args.func(args)
As showed in this example, you will have to change your functions a little bit:
They will take only one argument called args (created in the main function with args = argparser.parse_args())
And then, in each function, you will get the needed parameters by their names (the ones added with add_argument, like in get_data_3_parser.add_argument('t_id'). So, to get the argument called t_id, you will write t_id = args.t_id.
argparse documentation: https://docs.python.org/3/library/argparse.html
argparse tutorial: https://docs.python.org/3/howto/argparse.html
From the documentation:
Many programs split up their functionality into a number of
sub-commands, for example, the svn program can invoke sub-commands
like svn checkout, svn update, and svn commit. Splitting up
functionality this way can be a particularly good idea when a program
performs several different functions which require different kinds of
command-line arguments. ArgumentParser supports the creation of such
sub-commands with the add_subparsers() method.
Related
Lets say you have the following code
def set_args():
#Set Arguments
parser = argparse.ArgumentParser()
parser.add_argument("-test", help = 'test')
return parser
def run_code():
def fileCommand():
print("the file type is\n")
os.system("file " + filename).read().strip()
def main():
parser = set_args()
args = parser.parse_args()
what is best way to call that fileCommand() function from def main():?
I have the following code which of course doesn't work:
def main():
parser = set_args()
args = parser.parse_args()
#If -test,
if args.test:
filename=args.test
fileCommand()
So if you were to run python test.py -test test.txt it should run the file command on it to get the file type
I know if I keep messing around I'll get it one way or another, but I typically start to over complicated things so its harder down the line later. So what is the proper way to call nested functions?
Thanks in advance!
Python inner functions (or nested functions) have many use cases but in non of them, accessing through the nested function from outside is an option. A nested function is created to have access to the parent function variables and calling it from outside of the parent function is in conflict with principle.
You can call the function from the parent function or make it a decorator:
Calling the nested function from the parent would be:
def run_code(filename):
def fileCommand():
print("the file type is\n")
os.system("file " + filename).read().strip()
fileCommand()
If you describe more about your use case and the reason why you want to run the code like this, I can give more details about how you can implement your code.
This may be a bit confusing so I'll try and clarify my points with visualizations and examples
Say I have a driver code and two separate python functions.
Function A is the following:
def FuncA(foo, bar):
output = foo + bar
return output
Function B is the following:
def FuncB(foobar, x):
output = (foobar)/(x)
return output
I want to create a driver code that can call either of these functions and base the arguments it parses from the user (via argparse) off of the function itself.
This means the following code is NOT allowed:
parser = argparse.ArgumentParser()
parser.add_argument('-function', choices=["FuncA","FuncB"])
*blah blah*
if function == "FuncA":
parser.add_argument('-foo')
parser.add_argument('-bar')
elif:
parser.add_argument('-foobar')
parser.add_argument('-x')
args = parser.parse_args()
or anything of a similar sort.
The point is to create what is essentially a "Plug 'n Play" system where codes can be added modularly to this driver code and modified individually (including potentially changing inputs) without having to modify the driver code itself (or with potential minimal modification to the driver code)
We are looking for a way to intelligently parse the arguments that a function has and use this to create argparse arguments for a given run case.
A visual example of what we are doing (with the context of a visualization code)
The ideal command-line invocation for our example would look something like this:
python3 driverCode.py funcA -foo 10 -bar 7
OR
python3 driverCode.py funcB -foobar 8 -x 2
and while this invocation could usually be accomplished with grouping, I don't believe this will handle the issue of dynamically fetching the -foo or -bar portion of the argument via the function.
I have a Python main program that imports another module (called actions) with multiple functions. The main program should run some things, get a string (i.e. goto(114)) and then run actions.goto(114), in which 114 is the argument to the function goto(x) in actions.
I've tried the obvious which was just trying to run the string but that did not work. I've also find the globals() method which would work if the goto(x) was inside my main module and I've also found the getattr method, but in this case I haven't found any example in which I pass the function name and argument so I'm kind of lost here.
#main.py
import actions
def main():
getc = 'goto(114)'
result = actions.getc #this would be actions.goto(114)
print result
#actions.py
def goto(x):
#code
return something
The actual program gets the string from a .txt file that another program wrote, I just made the example that way so that its simple to understand.
One option you could use is __getattribute__ on the action class to get the function goto, then call it with the encompassing argument. You'd need to parse it like so:
import re
import action
getc = 'goto(114)'
func, arg = re.search('(\w+)\((\d+)\)', 'goto(114)').groups()
# f is the function action.goto with the argument 114 supplied as an int
# __getattribute__ allows you to look up a class method by a string name
f = action.__getattribute__(func)
# now you can just call it with the arg converted to int
result = f(int(arg))
The regex might need to be refined a bit, but it's looking for the name of the calling function, and the arguments wrapped in parentheses. The __getattribute__ will get the function object from action and return it uncalled so you can call it later.
For multiple arguments you can leverage the ast library:
import re
import ast
# I'm going to use a sample class as a stand-in
# for action
class action:
def goto(*args):
print(args)
getc = 'goto(114, "abc", [1,2,3])'
func, args = re.search('(\w+)\((.*)\)', getc).groups()
# will convert this into python data structures
# safely, and will fail if the argument to literal_eval
# is not a python structure
args = ast.literal_eval('(%s)' % args)
f = getattr(action, func)
f(*args)
# 114, "abc", [1,2,3]
The easier option (proceed with caution) would be to use eval:
cmd = 'action.%s' % getc
result = eval(cmd)
Note that this is considered bad practice in the python community, though there are examples in the standard library that use it. This is not safe for un-validated code, and is easily exploited if you don't monitor your source file
I have a main function specified as entry point in my package's setup.py which uses the argparse package in order to pass command line arguments (see discussion here):
# file with main routine specified as entry point in setup.py
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument('a', type=str, help='mandatory argument a')
args = parser.parse_args()
Ideally, I would like to use the same main function in the package's tests as suggested here. In the latter context, I would like to call the main function from within the test class and set (some of) the command line arguments prior to the function call (which otherwise will fail, due to missing arguments).
# file in the tests folder calling the above main function
class TestConsole(TestCase):
def test_basic(self):
set_value_of_a()
main()
Is that possible?
The argparse module actually reads input variables from special variable, which is called ARGV (short from ARGument Vector). This variable is usually accessed by reading sys.argv from sys module.
This variable is a ordinary list, so you can append your command-line parameters to it like this:
import sys
sys.argv.extend(['-a', SOME_VALUE])
main()
However, messing with sys.argv at runtime is not a good way of testing.
A much more cleaner way to replace the sys.argv for some limited scope is using unittest.mock.patch context manager, like this:
with unittest.mock.patch('sys.argv'. ['-a', SOME_VALUE]):
main()
Read more about unittest.mock.patch in documentation
Also, check this SO question:
How do I set sys.argv so I can unit test it?
#William Fernandes: Just for the sake of completeness, I'll post the full solution in the way that was suggested by (checking for an empty dict not kwargs is None):
def main(**kwargs):
a = None
if not kwargs:
parser = argparse.ArgumentParser()
parser.add_argument('a', type=str, help='mandatory argument a')
args = parser.parse_args()
a = args.a
else:
a = kwargs.get('a')
print(a)
From within the test class the main function can then be called with arguments:
# file in the tests folder calling the above main function
class TestConsole(TestCase):
def test_basic(self):
main(a=42)
The call from the command line without kwargs then requires the specification of the command line argument a=....
Add kwargs to main and if they're None, you set them to the parse_args.
I want to use nose and coverage in my project. When I run nose with --with-coverage argument, my programs argument-parsing module goes nuts because "--with-coverage" isn't a real argument according to it.
How do I turn the argparse off, but during testing only? Nose says all my tests fail because of the bad argument.
I actually just ran into this issue myself the other day. You don't need to "disable" your parsing module or anything. What you can do is change the module that uses argparse to ignore those arguments it receives that it doesn't recognize. That way they can still be used by other scripts (for example if your command-line call passes secondary arguments to another program execution).
Without your code, I'll assume you're using the standard parse_args() method on your argparse.ArgumentParser instance. Replace it with parse_known_args() instead.
Then, whenever you subsequently reference the parsed-arguments Namespace object, you'll need to specify and element, specifically 0. While parse_args() returns the args object alone, parse_known_args() returns tuple: the first element is the parsed known arguments, and the latter element contains the ignored unrecognized arguments (which you can later use/pass in your Python code, if necessary).
Here's the example change from my own project:
class RunArgs(object):
'''
A placeholder for processing arguments passed to program execution.
'''
def __init__(self):
self.getArgs()
#self.pause = self.args.pause # old assignment
self.pause = self.args[0].pause # new assignment
#...
def __repr__(self):
return "<RunArgs(t=%s, #=%s, v=%s)>" % (str(x) for x in (self.pause,self.numreads,self.verbose))
def getArgs(self):
global PAUSE_TIME
global NUM_READS
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--pause', required=False,
type=self.checkPauseArg, action='store', default=PAUSE_TIME)
parser.add_argument('-n', '--numreads', required=False,
type=self.checkNumArg, action='store', default=NUM_READS)
parser.add_argument('-v', '--verbose', required=False,
action='store_true')
#self.args = parser.parse_args() # old parse call
self.args = parser.parse_known_args() # new parse call
#...
I've read that you can use nose-testconfig, or otherwise use mock to replace the call (not test it). Though I'd agree with #Ned Batchelder, it begs questioning the structure of the problem.
As a workaround, instead of running nose with command-line arguments, you can have a .noserc or nose.cfg in the current working directory:
[nosetests]
verbosity=3
with-coverage=1
Though, I agree that parse_known_args() is a better solution.
It sounds like you have tests that run your code, and then your code uses argparse which implicitly pulls arguments from sys.argv. This is a bad way to structure your code. Your code under test should be getting arguments passed to it some other way so that you can control what arguments it sees.
This is an example of why global variables are bad. sys.argv is a global, shared by the entire process. You've limited the modularity, and therefore the testability, of your code by relying on that global.