Недавно возникла такая задача: требовалось из Python скрипта запустить дочерний процесс, тоже Python скрипт, и получить от него некоторые данные. В моём случае это был некий словарь который мог быть сериализован в JSON формат, но это не так важно.
Какие есть варианты это сделать?
1️⃣ Передать дочернему процессу путь к файлу куда и будет записан результат.
После завершение дочернего процесса просто читаем данные из файла.
✅ легко и понятно, все так умеют делать
✅ можно перемещаться по файлу через seek
✅ можно прочитать когда-нибудь потом
❌ обращение к файловой системе, бывает относительно не быстро
❌ какое-то время файл будет доступен любому процессу, небезопасно
❌ только полная запись данных перед чтением (на самом деле есть вариант чтения во время записи, но это не то что мы хотим делать😖)
2️⃣ TCP/UDP сокет
✅ универсально, даже для неродственных процессов
✅ нет обращения в файловой системе (Unix-сокеты это почти файлы но всё равно не совсем)
✅ можно стримить данные
❌ нужна какая-то система авторизация чтобы обезопасить доступ
❌ оверхед для простой передачи данных, особенно если процесс дочерний. Требуется поднятие сервера и организция клиента со всеми вытекающими зависимостями и конструкциями
3️⃣ Парсить аутпут дочернего процесса.
✅ быстро, так как пайпы работают через оперативную память
✅ нет обращения к файловой системе и всех действий с этим связанных
✅ пайп привязан к файловым дескрипторам конкретных процессов, и доступ к нему могут получить только те процессы, которые унаследовали этот дескриптор (или получили другим способом)
✅ передача данных в режиме стрима
❌ неудобно если дочерний процесс пишет логи в stdout, нужна какая-то логика выделения только нужного или как-то отключать логи в надежде что никто другой туда ничего не напишет.
❌ нельзя перемещаться через seek
Если у вас взаимодействие с дочерним процессом, то есть самый простой вариант - кастомный пайп!✨
Это как
Для простоты примера сделаем один пайп. Дочерний процесс должен что-то прислать в родительский процесс.
👮♂️РОДИТЕЛЬСКИЙ ПРОЦЕСС
1. Создаем новый пайп
2. Запускаем дочерний процесс передавая ему номер файла
3. Читаем данные
Чтение прекратится когда файл закроется, за это отвечает контекстный менеджер
Стандартные пайпы тоже можно прочитать
👶 Переходим к коду дочернего процесса.
1. Получаем номер дескриптора
Пишем в него данные
Вот и всё, мы сделали коммуникацию между двумя процессами через кастомный пайп ⭐️
Быстро, легко, безопасно!
С помощью двух пайпов можно ораганизовать передачу сообщений между процессами в обе стороны.
Пример с JSON можно глянуть здесь↗️
#tricks
Какие есть варианты это сделать?
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
Gist
custom-pipe-example.py
GitHub Gist: instantly share code, notes, and snippets.
🔥11👍4❤2
Быстрый встроенный профайлинг на Linux с помощью time
Покажет время выполнения процесса
Но это встроенная команда из моей оболочки. Есть такая же GNU-утилита и она может показывать больше информации. Но нужно вызывать по абсолютному пути, так как builtin команда имеет бОльший приоритет.
Кроме времени исполнения будет также показано много другой полезной информации
- эффективность использования CPU (в %)
- максимальный объем занятой памяти
- обращения к файлам
- код выхода
И другие сведения.
#tricks
time python -c 'for i in range(10**7): i**2'
Покажет время выполнения процесса
real 0m2,470s
user 0m2,405s
sys 0m0,074s
real - Общее время, прошедшее с момента запуска до завершения программы. Включая время ожидания I\O или переключения контекста.user - Количество времени, которое CPU потратил на выполнение кода самой программы в пользовательском режиме.sys - Количество времени, которое CPU потратил на выполнение системных вызовов (операций ядра, таких как чтение/запись файлов, управление памятью) от имени программы.Но это встроенная команда из моей оболочки. Есть такая же GNU-утилита и она может показывать больше информации. Но нужно вызывать по абсолютному пути, так как builtin команда имеет бОльший приоритет.
/usr/bin/time -v python -c 'for i in range(10**7): i**2'
Command being timed: "python -c for i in range(10**7): i**2"
User time (seconds): 2.38
System time (seconds): 0.07
Percent of CPU this job got: 100%
...
Кроме времени исполнения будет также показано много другой полезной информации
- эффективность использования CPU (в %)
- максимальный объем занятой памяти
- обращения к файлам
- код выхода
И другие сведения.
#tricks
🔥7❤2👍2