5

I am passing a list from a subprocess to the parent process and in the parent process I want to add this to an already existing list. I did this:

subprocess_script.py:

def func():

    list = []
    list.append('1')
    list.append('2')

    print'Testing the list passing'
    print '>>> list:',list

if __name__ == '__main__':
     func()

parent_script.py:

list1 = []
list1.append('a')
list1.append('b')
ret = subprocess.Popen([sys.executable,"/Users/user1/home/subprocess_script.py"],stdout=subprocess.PIPE)
ret.wait()

return_code = ret.returncode
out, err = ret.communicate()
if out is not None:
        for line in out.splitlines():

            if not line.startswith('>>>'):
                continue
            value = line.split(':',1)[1].lstrip()
            list1.extend(value)
print 'Final List: ',list1

But when I execute this I do not get the desired output. The final list that I want should be : ['a','b','1','2']. But I get ['a', 'b', '[', "'", '1', "'", ',', ' ', "'", '2', "'", ']'] which is wrong. What am I doing wrong here?

Jason Donnald
  • 2,256
  • 9
  • 36
  • 49
  • 1
    FYI it's not good practice to use variable names that hide Python builtins (here, `list`). – a p Jul 06 '15 at 17:18

3 Answers3

2

The problem is that after your split and lstrip calls, value is still a string, not a list yet. You can stop the script including a pdb.set_trace line and inspect it like this:

if not line.startswith('>>>'):
    continue
import pdb; pdb.set_trace()
value = line.split(':', 1)[1].lstrip()
list1.extend(value)

And then run the code:

❯ python main.py
> /private/tmp/example/main.py(19)<module>()
-> value = line.split(':', 1)[1].lstrip()
(Pdb) line
">>> list: ['1', '2']"
(Pdb) line.split(':', 1)[1].lstrip()
"['1', '2']"

You can evaluate that string into a Python list by using the ast.literal_eval function, like this:

(Pdb) import ast
(Pdb) ast.literal_eval(line.split(':', 1)[1].lstrip())
['1', '2']

Now list1 can be extended with this value.

From the Python 2.7 documentation:

ast.literal_eval(node_or_string)

Safely evaluate an expression node or a Unicode or Latin-1 encoded string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

This can be used for safely evaluating strings containing Python values from untrusted sources without the need to parse the values oneself. It is not capable of evaluating arbitrarily complex expressions, for example involving operators or indexing.

Community
  • 1
  • 1
logc
  • 3,813
  • 1
  • 18
  • 29
  • is it required to use `ipdb`? Is there no other way to do without that? – Jason Donnald Jul 06 '15 at 16:39
  • @JasonDonnald : no, `ipdb` is not at all required; I just wanted to state precisely what I did. But since this might confuse you, and `pdb` is the standard tool shipped with Python, I just re-did the debugging in `pdb`. It's the same. – logc Jul 06 '15 at 16:45
  • thanks I got it! Can you provide a short explanation as to what `ast.literal_eval()` does to solve this? – Jason Donnald Jul 06 '15 at 16:47
1

You are doing it wrongly.

When you do - print '>>> list:',list . It would print -

>>> list: [1, 2]

And when you do - value = line.split(':',1)[1].lstrip() , value would become the string -

'[1, 2]'

And when extending list1 with value , each character in value would be added as a new element in list1 (because it would iterate over each element of the string, which is each character and add them to the list1).

When creating the value , you want to remove the first [ as well as the trailed ] and then split them based on , .

Example code -

value = line.split(':',1)[1].lstrip().lstrip('[').rstrip(']').replace("'",'').replace(" ",'').split(',')

The above code is a very big hack , better would be to use ast.literal_eval as @logc mentioned in his answer -

import ast
value = ast.literal_eval(line.split(":",1)[1].lstrip())

But please be vary, that ast.literal_eval would evalutate the expression and return the result, you should use it with care.

Anand S Kumar
  • 88,551
  • 18
  • 188
  • 176
  • what would be a better way to pass a list from subprocess to parent process as a list itself and then add it to another list in parent process? – Jason Donnald Jul 06 '15 at 16:36
  • I only know about this way, try the value line I gave. – Anand S Kumar Jul 06 '15 at 16:37
  • also when I use the update you mentioned I get final list as - `['a', 'b', "'1'", " '2'"]`. The last two elements have an extra `"` around them – Jason Donnald Jul 06 '15 at 16:38
  • @JasonDonnald : as @AnandSKumar tells you, using `eval` functions is always a bit of a security concern. `ast.literal_eval` promises to be secure, but you might be better off using a standard data representation, like JSON. – logc Jul 06 '15 at 16:54
1

Use a standard serialization data format, like JSON:

script.py

import json


def func():

    lst = []
    lst.append('1')
    lst.append('2')

    print json.dumps(lst)  ## <-- `dumps` dumps to a string

if __name__ == '__main__':
    func()

main.py

import sys
import os
import subprocess
import json

list1 = []
list1.append('a')
list1.append('b')
ret = subprocess.Popen([sys.executable, os.path.join(os.getcwd(), "script.py")], stdout=subprocess.PIPE)
ret.wait()

return_code = ret.returncode
out, err = ret.communicate()
line = next(line for line in out.splitlines())
value = json.loads(line)  ## <-- `loads` loads from a string
list1.extend(map(str, value))
print 'Final List: ', list1

The map(str, value) is just aesthetic: it is there to have a uniform list, because json.dumps will produce Unicode strings by default, and your previous list1 elements are not Unicode strings.

Also, I removed the whole header-printing and line-skipping parts of the code. You are just doing your life more difficult with them :)

logc
  • 3,813
  • 1
  • 18
  • 29
  • if suppose there are multiple print statements in the first script (which dumps the list) and out of those only one is with the dump then how can I separate that in the main script from other print statements? In my post if you notice I have two print statements in subprocess_script.py. To separate them I was adding a unique identifier `>>>` in the beginning of my desired print statement and then in parent script I was doing a check for that to skip other print statements – Jason Donnald Jul 06 '15 at 18:21