Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Launch a python script from another script, with parameters in subprocess argument

To launch a python script (it is needed for running an OLED display) from terminal, I have to use the following bash commands: python demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20. Those parameters after .py are important, otherwise, the script will run with default settings and in my case, the script will not launch with default settings. Thus, those parameters are needed.

The problem arises when I need to launch my script from another python script, (instead of using bash commands on terminal). To launch one of my python script from a parent script. I have used:

import subprocess # to use subprocess 

p = subprocess.Popen(['python', 'demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20'])

in my parent script but I got an error stating:

python: can't open file 'demo_oled_v01.py --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20': [Errno 2] No such file or directory

I suspect that adding the parameters --display ssd1351 --width 128 --height 128 --interface spi --gpio-data-command 20 after .py may be causing difficulty in launching the script. As mentioned, these parameters are otherwise essential for me to include for launching with bash commands on terminal. How can I use subprocess with the required parameters to launch this script?

like image 434
Craver2000 Avatar asked Dec 01 '22 14:12

Craver2000


2 Answers

The subprocess library is interpreting all of your arguments, including demo_oled_v01.py as a single argument to python. That's why python is complaining that it cannot locate a file with that name. Try running it as:

p = subprocess.Popen(['python', 'demo_oled_v01.py', '--display',
'ssd1351', '--width', '128', '--height', '128', '--interface', 'spi',
'--gpio-data-command', '20'])

See more information on Popen here.

like image 146
Evan Rose Avatar answered Dec 03 '22 05:12

Evan Rose


This started as a comment thread, but got too long and complex.

Calling Python as a subprocess of Python is an antipattern. You can often fruitfully avoid this by refactoring your Python code so that your program can call the other program as a simple library (or module, or package, or what have you -- there is a bit of terminology here which you'll want to understand more properly ... eventually).

Having said that, there are scenarios where the subprocess needs to be a subprocess (perhaps it is designed to do its own signal handling, for example) so don't apply this blindly.

If you have a script like demo.py which contains something like

def really_demo(something, other, message='No message'):
    # .... some functionality here ...

def main():
    import argparse
    parser = argparse.ArgumentParser(description='Basic boilerplate, ignore the details.')
    parser.add_argument('--something', dest='something')  # store argument in args.something
    parser.add_argument('--other', dest='other')  # ends up in args.other
    # ... etc etc etc more options
    args = parser.parse_args()
    # This is the beef: once the arguments are parsed, pass them on
    really_demo(args.something, args.other, message=args.message)

if __name__ == '__main__':
    main()

Observe how when you run the script from the command line, __name__ will be '__main__' and so it will plunge into the main() function which picks apart the command line, then calls some other function -- in this case, real_demo(). Now, if you are calling this code from an already running Python, there is no need really to collect the arguments into a list and pass them to a new process. Just have your Python script load the function you want to call from the script, and call it with your arguments.

In other words, if you are currently doing

 subprocess.call(['demo.py', '--something', 'foo', '--other', value, '--message', 'whatever'])

you can replace the subprocess call with

 from demo import real_demo
 real_demo('foo', value, message='whatever')

Notice how you are bypassing the main() function and all the ugly command-line parsing, and simply calling another Python function. (Pay attention to the order and names of the arguments; they may be quite different from what the command-line parser accepts.) The fact that it is defined in a different file is a minor detail which import handles for you, and the fact that the file contains other functions is something you can ignore (or perhaps exploit more fully if, for example, you want to access internal functions which are not exposed via the command-line interface in a way which is convenient for you).

As an optimization, Python won't import something twice, so you really need to make sure the functionality you need is not run when you import it. Commonly, you import once, at the beginning of your script (though technically you can do it inside the def which needs it, for example, if there is only one place in your code which depends on the import) and then you call the functions you got from the import as many or as few times as you need them.

This is a lightning recap of a very common question. If this doesn't get you started in the right direction, you should be able to find many existing questions on Stack Overflow about various aspects of this refactoring task.

like image 25
tripleee Avatar answered Dec 03 '22 03:12

tripleee