#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. Спасибо за ответ, я все еще изучаю этот и другие аспекты. Вы правы в том, что сказали в более поздней части вашего комментария.