Как вернуть словарь в качестве возвращаемого значения функции, выполняемой в качестве подпроцесса, ее родительскому процессу?

#python #python-3.x #subprocess #popen #popen3

#python #python-3.x #подпроцесс #popen #popen3

Вопрос:

У меня есть два сценария parent.py и ребенок.py parent.py зовет ребенка.py как подпроцесс. Дочерний.у py есть функция, которая собирает определенный результат в словаре, и я хочу вернуть этот словарь обратно родительскому процессу. Я попытался распечатать этот словарь из дочернего процесса.py в его стандартный вывод, чтобы родительский процесс мог его прочитать, но тогда это мне не помогает, поскольку содержимое словаря считывается родительским в виде строк в отдельных строках.

Более того, как было предложено в комментариях, я попытался сериализовать словарь с помощью JSON при печати его в стандартном выводе, а также прочитать его из родительского с помощью JSON , это работает нормально, но я также печатаю много другой информации из дочернего элемента в его стандартный вывод, который в конечном итоге также считывается родительским ивсе смешивается.

Другое предложение, которое возникло, заключалось в записи результата от дочернего элемента в файл в каталоге и выполнении родительского чтения из этого файла. Это тоже сработало бы, но я бы запустил 100 экземпляров этого кода в Celery, и, следовательно, это привело бы к перезаписи того же файла другими экземплярами дочернего процесса.

Мой вопрос в том, что, поскольку у нас есть КАНАЛ, соединяющий два процесса, как я могу просто записать свой словарь непосредственно в КАНАЛ из дочернего процесса.py и получить его из parent.py

 # parent.py

import subprocess

proc = subprocess.Popen(['python3', 'child.py'],
                        stdin=subprocess.PIPE,
                        stdout = subprocess.PIPE
                        )
proc.comunicate()
result = proc.stdout
  
 #child.py

def child_function():
    result = {}
    result[1] = "one"
    result[2] = "two"
    print(result)
    #return result
    
if __name__ == "__main__":
    child_function()
  

Комментарии:

1. Чтобы передать словарь по каналу, вам необходимо его сериализовать, например, как JSON. Но почему вы просто не импортируете и не вызываете child_function() из parent.py ?

2. Вы можете использовать JSON для сериализации данных: print(json.dumps(result)) в дочернем. py и result = json.loads(proc.stdout) в parent.py .

3. @mkrieger1 Спасибо за ответ . Да, я хотел бы просто импортировать child_function() из parent, но я нахожусь в ситуации, из-за которой мне приходится вызывать дочерний процесс. py как подпроцесс, поскольку я запускаю родительский процесс как root, но некоторые функции присутствуют в дочернем процессе. py не работает как пользователь root, поэтому я вызываю дочерний процесс. py через подпроцесс с отдельным пользователем, не являющимся root.

4. @IonutTicus Спасибо за предложение, я попробую это.

5. @IonutTicus Нет ли какого-либо способа напрямую передать dicitonary от дочернего процесса. py родительскому процессу через КАНАЛ вместо того, чтобы «сначала печатать его в стандартный вывод дочернего элемента», а затем заставлять родительский элемент считывать его из стандартного вывода дочернего элемента. Я имею в виду, что мое намерение заключается в том, зачем печатать его в стандартном выводе, когда мы уже подключены через КАНАЛ, почему мы не можем просто передать это значение напрямую через КАНАЛ его родителю? Причина, по которой я ищу это, заключается в том, что я также печатаю много других материалов из дочернего элемента в его стандартный вывод (отладочная информация), и эти другие вещи будут мешать словарю, если я его распечатаю.

Ответ №1:

Попросите родительский файл создать FIFO (именованный канал) для дочернего процесса:

 with os.mkfifo(mypipe) as pipe:
    proc = subprocess.Popen(['python3', 'child.py', 'mypipe'],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    print(pipe.read())
  

Теперь дочерний элемент может это сделать:

 pipe_path = # get from argv
with open(pipe_path, 'w') as pipe:
    pipe.write(str(result))
  

Это позволяет отделить ваше сообщение от stdin / stdout / stderr.

Ответ №2:

Подпроцесс, выполняющий Python, ничем не отличается от подпроцесса, выполняющего что-то другое. Python не знает и не заботится о том, что другая программа также является программой Python; у них нет доступа к переменным, памяти, состоянию выполнения или другим внутренним функциям друг друга. Просто представьте, что подпроцесс представляет собой монолитный двоичный файл. Единственные способы, которыми вы можете взаимодействовать с ним, — это отправлять и получать байты (которые могут быть строками, если вы согласны с кодировкой символов) и сигналы (чтобы вы могли отключить свой подпроцесс или вызвать какой-либо другой сигнал, который он может перехватить и обработать — например, таймер; вы получаете ровно один битинформация о том, когда истекает таймер, и что вы делаете с этим битом, зависит от получателя сигнала).

«Сериализовать» информацию означает закодировать ее таким образом, чтобы получатель мог ее десериализовать. Хорошим примером является JSON; вы можете передать структуру, состоящую из (возможно, вложенной структуры) словаря или списка, в виде текста, и получатель будет знать, как отобразить этот поток байтов в ту же структуру.

Когда и отправитель, и получатель используют одну и ту же версию Python, вы также можете использовать pickles; pickle — это собственный формат Python, который позволяет передавать более богатую структуру. Но если ваши потребности скромны, я бы просто выбрал JSON.

parent.py :

 import subprocess
import json

# Prefer subprocess.run() over bare-bones Popen()
proc = subprocess.run(['python3', 'child.py'],
    check=True, capture_output=True, text=True)
result = json.loads(proc.stdout)
  

child.py :

 import json
import logging

def child_function():
    result = {}
    result[1] = "one"
    result[2] = "two"
    loggging.info('Some unrelated output which should not go into the JSON')
    print(json.dumps(result))
    #return result
    
if __name__ == "__main__":
    logging.basicConfig(level=logging.WARNING)
    child_function()
  

Чтобы избежать смешивания JSON с другим выводом, выведите другой вывод в стандартную ошибку вместо стандартного вывода (или придумайте способ встроить его в JSON в конце концов). logging Модуль представляет собой удобный способ сделать это, с дополнительным бонусом, который вы можете легко отключить, частично или полностью (приведенный выше пример демонстрирует ведение журнала, которое отключено logging.basicConfig , потому что оно выбирает только печать сообщений с приоритетом WARNING или выше, что исключает INFO ). Родительский процесс получит эти сообщения proc.stderr .

Ответ №3:

Вы можете получить результаты через файл.

parent.py:

 import tempfile
import os
import subprocess
import json


fd, temp_file_name = tempfile.mkstemp() # create temporary file
os.close(fd) # close the file
proc = subprocess.Popen(['python3', 'child.py', temp_file_name]) # pass file_name
proc.communicate()
with open(temp_file_name) as fp:
    result = json.load(fp) # get dictionary from here
os.unlink(temp_file_name) # no longer need this file
  

child.py:

 import sys
import json


def child_function(temp_file_name):
    result = {}
    result[1] = "one"
    result[2] = "two"
    with open(temp_file_name, 'w') as fp:
        json.dump(result, fp)

    
if __name__ == "__main__":
    child_function(sys.argv[1]) # pass the file name argument
  

Комментарии:

1. Спасибо за ответ, даже я думал об этом решении, но дело в том, что все это будет отправлено в celery (очереди задач), и почти 100 экземпляров этого кода будут выполняться параллельно, и я боюсь, что это определенно приведет к перезаписи в этот файл, потому что, опять же, 100экземпляры будут запускать один и тот же код.

2. Мне нужно было бы просмотреть больше кода, но из вашего описания неясно, почему у каждого экземпляра не будет своего уникального временного файла. Другими словами, я не вижу, чтобы это сильно отличалось от ответа, в котором использовался subprocess.run) и извлекался JSON из стандартного вывода, по крайней мере, в том, что касается перезаписи чего-либо. Это решение может быть более простым, и я предложил это как еще один способ, который не потребует от вас изменения способа использования стандартного вывода или любого другого аспекта вашего кода.

3. Спасибо за ответ, я все еще изучаю этот и другие аспекты. Вы правы в том, что сказали в более поздней части вашего комментария.