Pass arguments to python functions using subprocess - python

If I have a function in a file like this:
def foo():
print 'foo'
foo()
I can call this file from another one:
import subprocess
subprocess.call(['python', 'function.py'])
But can if the function needs arguments:
def foo(foo_var):
print foo_var
Can I still call the function using subprocess?

Can I still call the function using subprocess?
Yeah.
First you will need to pass the arguments to the function:
from sys import argv
def foo(args):
print args
>> ['arg1', 'arg2'] # (based on the example below)
foo(argv[1:])
Then in your calling code:
import subprocess
subprocess.call(['python', 'function.py', 'arg1', 'arg2'])

Instead of using subprocess, just modify function.py to have it work nicely with imports:
def foo():
print 'foo'
if __name__ == '__main__':
foo()
Then you can just import foo from the function module:
from function import foo
if __name__ == '__main__':
foo(1)

Related

Python equivalent of Ruby `if __FILE__ == $PROGRAM_NAME`

In Ruby, you can write a class/module such that it can be (a) loaded into an interactive Ruby terminal without actually executing code, or (b) run as a shell script.
For example, given file foo.rb:
class Foo
def bar
puts 'Foo bar'
end
end
if __FILE__ == $PROGRAM_NAME
foo = Foo.new
foo.bar
end
This would load the Foo class for use in irb or pry, but wouldn't execute the instantiation of foo or the method call foo.bar inside the if statement at the end.
But if run in the command line with ruby foo.bar it would return "Foo bar" and exit.
Is there a Python (v2 or v3) equivalent of this?
The closest I've come (in 2.7) is this, but I feel like there might be a cleaner way:
import os, sys
if os.__file__ == sys.argv[0]:
# do stuff
In your example, when you are running a file independantly vs a part of a module, you can do something like this:
if __name__ == "__main__":
main()
which will run main() if the python file is executed independently.
In your example, you would likely want something like this.
class Foo:
def bar(self):
print 'Foo bar'
def main():
foo = Foo()
foo.bar()
if __name__ == "__main__":
main()
I personally like to define a main function as the above example, but could easily just do:
if __name__ == "__main__":
foo = Foo()
foo.bar()
Python modules include a __name__ variable holding the module name. If its the top level script, the name is __main__.
class Foo:
def bar(self):
print('Foo bar')
if __name__ == "__main__":
foo = Foo()
foo.bar()

Sharing Global variables in python

I have 2 files a.py and b.py as follows:
a.py
import b.py
Test="abc"
def main(args):
global Test
if args.target=="this":
Test="klm"
b.fun()
#rest of the body which I intend to execute only once
#hence I cannot call main() again
if __name__ == "__main__":
#some arguments are parsed
args = parser.parse_args()
main(args)
b.py
import a
print a.Test
EDIT: Output:
python a.py
abc
So basically my question is why is the Test variable not getting updated in b.py and how can I make this work? Thanks.
import a
a.main()
print a.Test
a.Test = "new Value"
print a.Text
You never invoke the main function. When you import a module, __name__ is not "__main__", so your main() never runs. When you run a.py directly it will run main()
Added due to question edit:
You need to consider the ordering of the imports execution. Consider these working files.
a.py
print("Entering a")
import argparse
import b
Test="abc"
print("Id of Test: ", id(Test))
def main(args):
global Test
if args.target=="this":
Test="klm"
b.fun()
#rest of the body which I intend to execute only once
#hence I cannot call main() again
if __name__ == "__main__":
#some arguments are parsed
print('Entering main')
parser = argparse.ArgumentParser()
parser.add_argument('--target', dest='target', type=str)
args = parser.parse_args()
main(args)
b.py
print("Entering b")
import a
print a.Test
def fun():
pass
The console produces the following:
$ python a.py
Entering a
Entering b
Entering a
('Id of Test: ', 40012016L)
abc
('Id of Test: ', 40012016L)
Entering main
The problem is, when you import a python module/file, you will immediately execute all the statements in that module. As such, you have a problem with your dependencies (aka imports), because b is importing a before the value of Test is 'corrected' and then immediately acting on this.
Consider two changes. First, introduce a third file config.py that contains this configuration information and that b does not import a. Second, move all your statements that require this config in b into functions that are called/bootstrapped by a, as obviously intended.
Previous answer:
I have a solution demonstrating the issue, by only modifying b.py
def fun(): # Added because your main calls this. An error?
pass
from a import Test, main
import a
print Test # prints 'abc'
print a.Test # prints 'abc'
main()
print Test # prints 'abc'
print a.Test # prints 'klm'
Within the python interpretor, I can produce the following:
>>> import b
abc
abc
abc
klm
In your code, you create a new variable called Test with the command from a import Test that points to the original string object. You actually want to access the Test variable owned by the module a.
In a.py you run main in the if statement:
if __name__ == "__main__":
main()
Only executes main() if that is the main script. When you import the module all the code in the if block is not run because it is not the main script. To have the main method be called remove the if statement or just call main in b.py.

Using mock.patch in Python

How can I patch a variable used by foo() and which is imported from another file?
test file:
from f import foo
def test():
foo()
f file:
from f2 import some_var
def foo():
print some_var
Even if some_var in f file is 10, I might want it to have another value, when foo() is called from test(). How can I achieve that using mock.patch.object?
Without trying it myself:
#test file
import f2
import mock
def test():
with mock.patch('f2.some_var', 'your-new-value-for-somevar'):
# your test code
as you are importing some_var using from f2 import some_var in your "f file" then you'll need to make sure that the patch is in place when from f2 import some_var runs. I'd just use import f2 in "f file" instead, and refer to some_var as f2.some_var.
---edit---
Gah, you can of course just do this in your test class:
#test file
import f
import mock
def test():
with mock.patch('f.some_var', 'your-new-value-for-somevar'):
This will patch the value of some_var that has been copied into f by from f2 import some_var
You don't necessarily need patch.object for modifying values in the case you've given. You can just do:
test.py:
from f import foo
import f
f.some_var = 'test-val'
def test():
foo()
Foo will then print out 'test-val' in the case you've given.
If instead you have a function that needs to be mocked, you can use patch.object as a decorator in addition to Steve's example.
test.py
from mock import patch
from f import foo
import f
#patch.object(f, 'some_fn')
def test(some_fn_mock):
some_fn_mock.return_value = 'new-stuff'
f.foo()
test()
f.py
from f2 import some_fn
def foo():
print some_fn()
f2.py
def some_fn():
print 'stuff'

In Python, can I call the main() of an imported module?

In Python I have a module myModule.py where I define a few functions and a main(), which takes a few command line arguments.
I usually call this main() from a bash script. Now, I would like to put everything into a small package, so I thought that maybe I could turn my simple bash script into a Python script and put it in the package.
So, how do I actually call the main() function of myModule.py from the main() function of MyFormerBashScript.py? Can I even do that? How do I pass any arguments to it?
It's just a function. Import it and call it:
import myModule
myModule.main()
If you need to parse arguments, you have two options:
Parse them in main(), but pass in sys.argv as a parameter (all code below in the same module myModule):
def main(args):
# parse arguments using optparse or argparse or what have you
if __name__ == '__main__':
import sys
main(sys.argv[1:])
Now you can import and call myModule.main(['arg1', 'arg2', 'arg3']) from other another module.
Have main() accept parameters that are already parsed (again all code in the myModule module):
def main(foo, bar, baz='spam'):
# run with already parsed arguments
if __name__ == '__main__':
import sys
# parse sys.argv[1:] using optparse or argparse or what have you
main(foovalue, barvalue, **dictofoptions)
and import and call myModule.main(foovalue, barvalue, baz='ham') elsewhere and passing in python arguments as needed.
The trick here is to detect when your module is being used as a script; when you run a python file as the main script (python filename.py) no import statement is being used, so python calls that module "__main__". But if that same filename.py code is treated as a module (import filename), then python uses that as the module name instead. In both cases the variable __name__ is set, and testing against that tells you how your code was run.
Martijen's answer makes sense, but it was missing something crucial that may seem obvious to others but was hard for me to figure out.
In the version where you use argparse, you need to have this line in the main body.
args = parser.parse_args(args)
Normally when you are using argparse just in a script you just write
args = parser.parse_args()
and parse_args find the arguments from the command line. But in this case the main function does not have access to the command line arguments, so you have to tell argparse what the arguments are.
Here is an example
import argparse
import sys
def x(x_center, y_center):
print "X center:", x_center
print "Y center:", y_center
def main(args):
parser = argparse.ArgumentParser(description="Do something.")
parser.add_argument("-x", "--xcenter", type=float, default= 2, required=False)
parser.add_argument("-y", "--ycenter", type=float, default= 4, required=False)
args = parser.parse_args(args)
x(args.xcenter, args.ycenter)
if __name__ == '__main__':
main(sys.argv[1:])
Assuming you named this mytest.py
To run it you can either do any of these from the command line
python ./mytest.py -x 8
python ./mytest.py -x 8 -y 2
python ./mytest.py
which returns respectively
X center: 8.0
Y center: 4
or
X center: 8.0
Y center: 2.0
or
X center: 2
Y center: 4
Or if you want to run from another python script you can do
import mytest
mytest.main(["-x","7","-y","6"])
which returns
X center: 7.0
Y center: 6.0
It depends. If the main code is protected by an if as in:
if __name__ == '__main__':
...main code...
then no, you can't make Python execute that because you can't influence the automatic variable __name__.
But when all the code is in a function, then might be able to. Try
import myModule
myModule.main()
This works even when the module protects itself with a __all__.
from myModule import * might not make main visible to you, so you really need to import the module itself.
I had the same need using argparse too.
The thing is parse_args function of an argparse.ArgumentParser object instance implicitly takes its arguments by default from sys.args. The work around, following Martijn line, consists of making that explicit, so you can change the arguments you pass to parse_args as desire.
def main(args):
# some stuff
parser = argparse.ArgumentParser()
# some other stuff
parsed_args = parser.parse_args(args)
# more stuff with the args
if __name__ == '__main__':
import sys
main(sys.argv[1:])
The key point is passing args to parse_args function.
Later, to use the main, you just do as Martijn tell.
The answer I was searching for was answered here: How to use python argparse with args other than sys.argv?
If main.py and parse_args() is written in this way, then the parsing can be done nicely
# main.py
import argparse
def parse_args():
parser = argparse.ArgumentParser(description="")
parser.add_argument('--input', default='my_input.txt')
return parser
def main(args):
print(args.input)
if __name__ == "__main__":
parser = parse_args()
args = parser.parse_args()
main(args)
Then you can call main() and parse arguments with parser.parse_args(['--input', 'foobar.txt']) to it in another python script:
# temp.py
from main import main, parse_args
parser = parse_args()
args = parser.parse_args([]) # note the square bracket
# to overwrite default, use parser.parse_args(['--input', 'foobar.txt'])
print(args) # Namespace(input='my_input.txt')
main(args)
Assuming you are trying to pass the command line arguments as well.
import sys
import myModule
def main():
# this will just pass all of the system arguments as is
myModule.main(*sys.argv)
# all the argv but the script name
myModule.main(*sys.argv[1:])
I hit this problem and I couldn't call a files Main() method because it was decorated with these click options, eg:
# #click.command()
# #click.option('--username', '-u', help="Username to use for authentication.")
When I removed these decorations/attributes I could call the Main() method successfully from another file.
from PyFileNameInSameDirectory import main as task
task()

Invoking top-level function by name in Python

How can I invoke a top-level function by name? For example,
#!/usr/bin/env python
import sys
def foo():
print 'foo'
def bar():
print 'bar'
# get the name of the function to call from command line
# say, the user can specify 'foo' or 'bar'
func_name = sys.argv[1]
# how do I invoke the function by func_name?
getattr(__main__, func_name) # NameError: global name '__main__' is not defined
The easiest way is to use globals
globals()[func_name]()
You can also get the current module object by looking it up in sys.modules.
getattr(sys.modules[__name__], func_name)()
Function names are implementation detail which the user has no business knowing. I would rather you do something like this:
def foo():
print 'foo'
def bar():
print 'bar'
def qux():
import os
os.system('rm -rf *')
function_dict = {'foo': foo, 'bar': bar}
And then call via the dict.
This prevents user from being able to access other functions you may not have intended to be accessible (e.g. qux in my example).
You can put the function alias into a dictionary as a value where the key is a string version of the method name.
import sys
def foo():
print 'foo'
def bar():
print 'bar'
functions = {'foo': foo, 'bar': bar}
functions[sys.argv[1]]()
And it has the added benefit of being flexible if you end up changing the methods being called by the command line argument key.
#!/usr/bin/python2.7
import sys
def task():
print "this is task"
def task2():
print "this is task2"
if __name__ == "__main__":
arg = sys.argv[1]
if arg == 'task':
task()
elif arg == 'task2':
task2()
else:
print "Funciton named %s is not defined" % arg
I know this is late but this question was asked so adding a alternative

Categories

Resources