Недавно возникла такая задача: требовалось из Python скрипта запустить дочерний процесс, тоже Python скрипт, и получить от него некоторые данные. В моём случае это был некий словарь который мог быть сериализован в JSON формат, но это не так важно.
Какие есть варианты это сделать?
1️⃣Передать дочернему процессу путь к файлу куда и будет записан результат.
После завершение дочернего процесса просто читаем данные из файла.
✅ легко и понятно, все так умеют делать
✅ можно перемещаться по файлу через seek
✅ можно прочитать когда-нибудь потом
❌ обращение к файловой системе, бывает относительно не быстро
❌ какое-то время файл будет доступен любому процессу, небезопасно
❌ только полная запись данных перед чтением (на самом деле есть вариант чтения во время записи, но это не то что мы хотим делать😖)
2️⃣TCP/UDP сокет
✅ универсально, даже для неродственных процессов
✅ нет обращения в файловой системе (Unix-сокеты это почти файлы но всё равно не совсем)
✅ можно стримить данные
❌ нужна какая-то система авторизация чтобы обезопасить доступ
❌ оверхед для простой передачи данных, особенно если процесс дочерний. Требуется поднятие сервера и организция клиента со всеми вытекающими зависимостями и конструкциями
3️⃣ Парсить аутпут дочернего процесса.
✅ быстро, так как пайпы работают через оперативную память
✅ нет обращения к файловой системе и всех действий с этим связанных
✅ пайп привязан к файловым дескрипторам конкретных процессов, и доступ к нему могут получить только те процессы, которые унаследовали этот дескриптор (или получили другим способом)
✅ передача данных в режиме стрима
❌ неудобно если дочерний процесс пишет логи в stdout, нужна какая-то логика выделения только нужного или как-то отключать логи в надежде что никто другой туда ничего не напишет.
❌ нельзя перемещаться через seek
Если у вас взаимодействие с дочерним процессом, то есть самый простой вариант - кастомный пайп!✨
Это как stdout или stderr, но только еще один канал в котором не будет никаких логов и сообщений об ошибках.
Для простоты примера сделаем один пайп. Дочерний процесс должен что-то прислать в родительский процесс.
👮♂️РОДИТЕЛЬСКИЙ ПРОЦЕСС
1. Создаем новый пайп
import os. subprocess
read_fd, write_fd = os.pipe()
# важный момент! добавляем возможность наследовать дескриптор дочерним процессом. Обязательно после Python 3.4+ (PEP 446)
os.set_inheritable(write_fd, True)
2. Запускаем дочерний процесс передавая ему номер файла
process = subprocess.Popen(
[sys.executable, child_script, str(write_fd)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
close_fds=False # важный момент! это нужно, чтобы дочерний процесс сохранил все открытые дескрипторы, а не только стандартные потоки
)
os.close(write_fd) # закрываем дескриптор чтобы у родителя не висел открытый конец записи, иначе в читающем конце не наступит EOF
3. Читаем данные
with os.fdopen(read_fd, 'r') as data_pipe:
data = data_pipe.read()
print('RECEIVED:', data)
Чтение прекратится когда файл закроется, за это отвечает контекстный менеджер with в дочернем процессе.
Стандартные пайпы тоже можно прочитать
stdout_log, stderr_log = process.communicate()
print(stdout_log)
print(stderr_log)
👶 Переходим к коду дочернего процесса.
1. Получаем номер дескриптора
write_pipe_fd = int(sys.argv[-1])
Пишем в него данные
with os.fdopen(write_pipe_fd, 'w') as data_pipe:
data_pipe.write('Hello!')
data_pipe.flush()
Вот и всё, мы сделали коммуникацию между двумя процессами через кастомный пайп ⭐️
Быстро, легко, безопасно!
С помощью двух пайпов можно ораганизовать передачу сообщений между процессами в обе стороны.
Пример с JSON можно глянуть здесь↗️
#tricks