4

I have a code workflow in which from a main script(level 0) I call another script through subprocess. This subprocess script (level 1) in turn calls another script as a subprocess. Now from this level 2 subprocess script I want to return the value of a variable upto the main script(level 0). I have tried Popen.communicate() but I am not able to return the value. My current code is as:

main_script.py

def func():

    para = ['a','b']
    result = subprocess.Popen([sys.executable,"first_subprocess.py"]+para,stdout=subprocess.PIPE)
    result.wait()

    return_code = result.returncode
    out, err = sub_result.communicate()


    if return_code == 1:
        return return_code

    else:
        if out is not None:
            print 'return value: ', out


if __name__ == '__main__':

    func()

From above script is called first_subprocess.py which has:

def some_func():

    # some other code and multiple print statements

    para = ['a','b']
    result = subprocess.Popen([sys.executable,"second_subprocess.py"]+para,stdout=subprocess.PIPE)

    result.wait()
    out, err = result.communicate()
    return_code = sub_result.returncode
    if return_code == 0:
        return out


if __name__ == '__main__':

    some_func()

The second_subprocess.py returns a value like:

def test():
    # some other code and multiple print statements
    val = 'time'
    print 'returning value'
    return val   

if __name__ == '__main__':    

    test()

When I try above code I get all the print statements in the codes as an output but not the return value. Even if try to print the variable value in subprocess instead of doing a return it wont serve the purpose because there are multiple print statements.

How can I return the variable value in this case?

UPDATED VERSION:

After @Anthons suggestion I have modifed my first_subprocess.py script and main_script.py as follows:

first_subprocess.py:

def some_func():

   try:
    key = None
    if not (key is None):

       para = ['a','b']
       result = subprocess.Popen([sys.executable,"second_subprocess.py"]+para,stdout=subprocess.PIPE)

       sub_result.wait()
       out, err = sub_result.communicate()
       return_code = sub_result.returncode
       if return_code == 0:
       for line in out.splitlines():
           if not line.startswith('>>>'):
              continue
           print line
   else:
     sys.exit(0)
 except:
   return 1

Main_script.py:

if out is not None:
   for line in out.splitlines():
       if not line.startswith('>>>'):
          continue
      value = line.split(':',1)[1].lstrip()

print 'return value:',value`

When I execute above I get UnboundLocalError: local variable 'value' referenced before assignment at the print value command. It seems if I do not execute the code in level 1 script and do a sys.exit() then out in main script is neither empty nor none but it has some undefined value and thus the value variable doesn't get initialized and throws error

Jason Donnald
  • 2,256
  • 9
  • 36
  • 49
  • 1
    [import the modules instead of running them using `subprocess`](http://stackoverflow.com/q/30076185/4279) – jfs Jun 22 '15 at 15:50
  • related: [return value from one python script to another](http://stackoverflow.com/q/30664263/4279) – jfs Jun 22 '15 at 15:54
  • The updated first_subprocess.py cannot run. Maybe your indentation is not properly transferred. A general `try:` `except` like that hides any and all errors going on, that is very bad style. What is the `key = None` doing there? – Anthon Jun 23 '15 at 04:02
  • @Anthon that was by mistake. I have corrected the indentation. `key = None` is just a demo to show that the code will exit without executing underlying code. I have kept `try: except` to catch any errors that may come. The above piece of code has just a fragment of the actual code. I have kept try catch for rest of the code which I haven't posted above – Jason Donnald Jun 23 '15 at 13:22

1 Answers1

2

If you just want to return an integer value you can use the exit value. This is not the same a returning from some_func(), you would have to do sys.exit(integer_val).

If you want to return a string like time you should print that ( or write to sys.stdout ) and then in the calling process (level 1) parse the output from the subprocess and print it to its own stdout for level 0 to see it.

In your case level two should do something like:

def test():
    # some other code and multiple print statements
    val = 'time'
    print 'returning value:', val

if __name__ == '__main__':    
    test()

And at level 1 you would do:

def some_func():

    # some other code and multiple print statements

    para = ['a','b']
    result = subprocess.Popen([sys.executable,"second_subprocess.py"]+para,stdout=subprocess.PIPE)

    result.wait()
    out, err = result.communicate()
    return_code = sub_result.returncode
    if return_code == 0:
        print out

if __name__ == '__main__':
    some_func()

With that main_script.py has something to read from the invocation of your level 1 script.

I normally use subprocess.check_output() for these kind of passing on info. That throws an exception if the called process has a non-zero exit status (i.e. on error). I can also recommend that if the subprocess writes more info than just the variable, you make the output lines easily parseable by returning something unique at the beginning of the line (so you still can use print statements for debugging the individual scripts and get the right value from the output):

Level 2:

def test():
    # some other code and multiple print statements
    print 'debug: Still going strong'
    val = 'time'
    print '>>>> returning value:', val

if __name__ == '__main__':    
    test()

Level 1:

...
out, err = result.communicate()
for line in out.splitlines():
    if not line.startswith('>>>>'):
        continue
    print line
...

Level 0:

...
out, err = result.communicate()
for line in out.splitlines():
    if not line.startswith('>>>>'):
        continue
    try:
        value = line.split(':', 1)[1]
    except IndexError:
        print 'wrong input line', repr(line)
    print 'return value: ', value
...

The following files work together. Save them under their indicated names

lvl2.py:

# lvl2
import sys

def test():
    # some other code and multiple print statements
    print >> sys.stderr, 'argv', sys.argv[1:]
    print 'debug: Still going strong'
    val = 'time'
    print '>>>> returning value:', val
    return 0

if __name__ == '__main__':
    sys.exit(test())

lvl1.py:

# lvl1.py
import sys
import subprocess

def some_func():
    para = ['a','b']
    sub_result = subprocess.Popen(
        [sys.executable, "lvl2.py" ] + para,
        stdout=subprocess.PIPE)
    sub_result.wait()
    out, err = sub_result.communicate()
    return_code = sub_result.returncode
    if return_code == 0:
        for line in out.splitlines():
            if not line.startswith('>>>'):
                continue
            print line
    else:
        print >> sys.stderr, 'level 2 exited with' + return_code
    sys.exit(0)

if __name__ == '__main__':
    sys.exit(some_func())

lvl0.py:

# lvl0
import subprocess
import sys

def func():
    para = ['a','b']
    result = subprocess.Popen(
        [sys.executable, "lvl1.py"] + para,
        stdout=subprocess.PIPE)
    result.wait()
    return_code = result.returncode
    out, err = result.communicate()

    value = None
    if return_code == 0:
        for line in out.splitlines():
            if not line.startswith('>>>'):
                continue
            value = line.split(':',1)[1].lstrip()
            print
    else:
        print 'non-zero exit', return_code
    print 'return value:', value

if __name__ == '__main__':
    func()

Then run python lvl0.py to check the output to be

argv ['a', 'b']

return value: time

Now bring these under your revision control system and start making changes a few lines at a time, every time running python lvl0.py to check what you might have broken. Commit each revision so you can roll back to last "known good" state and slowly bring in the rest of your code.

Anthon
  • 69,918
  • 32
  • 186
  • 246
  • I tried using `print` instead of `return` like you have shown but the issue is that I already have multiple print statements in rest of the code in both the scripts. So, all these print statements also gets passed. – Jason Donnald Jun 22 '15 at 15:08
  • @JasonDonnald Make sure your variable line has something unique. Then in the calling program do `for line in out.splitlines()` and skip the lines not starting with the unique string. – Anthon Jun 22 '15 at 15:12
  • so if say like above I have `print 'returning value', val` as the unique line in level 2 script then how can I extract it in main_script code? If you could modfiy your answer it will be helpful – Jason Donnald Jun 22 '15 at 15:17
  • @JasonDonnald I extended my answer, but I have not run the exact scripts (fragments). I hope the fragments and where to put them make sense. You should be able to run the individual scripts and get full output and only the line starting with '>>>>' is passed on "through" level 1 – Anthon Jun 22 '15 at 15:19
  • when I try your above modifications I get `IndexError: list index out of range` at `_, value = line.split(':', 1)[1]` – Jason Donnald Jun 22 '15 at 15:29
  • @JasonDonnald I should have done `_, value = line.split(':', 1)` **or** `value = line.split(':', 1)[1]` not both – Anthon Jun 22 '15 at 15:35
  • When I try `_, value = line.split(':', 1)` I get error as `ValueError: need more than 1 value to unpack` and in case of `value = line.split(':', 1)[1]` I get `IndexError: list index out of range` – Jason Donnald Jun 22 '15 at 15:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/81197/discussion-between-anthon-and-jason-donnald). – Anthon Jun 22 '15 at 15:42
  • @JasonDonnald It looks like your line starting with '>>>>' doesn't have a colon in it. I added a try/except clause around it that prints the line if it cannot be "unpacked" – Anthon Jun 22 '15 at 15:52
  • I have updated my post with updated scripts in `UPDATED VERSION` section above. – Jason Donnald Jun 22 '15 at 21:43