Часть 2 Корониады

Введение

Эта серия статей предназначена для любого читателя Python, начиная с уровня новичка и описывает крошечные библиотеки, которые делают только одну важную вещь.

Эта библиотека sproc была частью моего набора инструментов в течение почти десяти лет в различных формах - в основном в неправильных формах.

Что такое sproc?

Выполнение команды в подпроцессе очень распространено - например, если вы хотите запустить сценарий оболочки или внешний двоичный файл из программы Python.

sproc - это крошечная однофайловая библиотека Python 3 для взаимодействия с подпроцессами.

import sproc
CMD = 'my-unix-command input.txt'
for ok, line in sproc.Sub(CMD) as sub
    if ok:
        use_data(line)
    else:
        handle_error(line)
sys.exit(sub.returncode)

Другой способ сделать то же самое:

returncode = sproc.call(CMD, use_data, handle_error)
sys.exit(returncode)

Зачем вам это использовать?

Встроенный Python subprocess полезен, но сложен

Python имеет встроенную библиотеку subprocess, которая очень мощная, но не совсем простая в использовании, особенно для процессов, которые выполняются долгое время или производят много вывода и используют как stdout, так и stderr по отдельности.

Наиболее часто используемые службы, такие как subprocess.run() и Popen.communicate(), запускают весь подпроцесс и возвращают результат после его завершения, но иногда вы хотите сообщать результаты по ходу работы, и, что еще хуже, есть буфер фиксированного размера, в котором может закончиться память. если ты это сделаешь.

Столько времени вам нужно перейти на subprocess.Popen(). Но получить данные из stdout и stderr по отдельности - нетривиальная задача. Чтобы сделать это правильно, вам нужно запустить два отдельных потока - если вы посмотрите исходный код Popen.communicate(), он делает именно это.

А есть ловушки ...

Ловушки с subprocess.Popen()

Это не абстрактные ловушки: это ловушки, в которые попал я лично.

Сама команда cmd должна быть строкой, если аргумент shell=True, и списком строк, если shell=False - и вы получите бесполезные сообщения об ошибках или предположительно неправильное поведение, если вы ошиблись.

Более того, команда представляет собой строку UTF8, но возвращаемые данные по умолчанию являются байтовыми строками. Ой!

Как только вы запомните кодировки, появится параметр encoding для subprocess.Popen() - за исключением того, что он не работает в версиях до Python 3.6 (хотя Python 3.5 очень близок к концу).

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

Введите sproc

sproc дает вам четыре способа запустить подпроцесс.

  • sproc.Sub(): перебирать строки по мере их поступления от stdout и stderr.
  • sproc.call(): запуск подпроцесса, обратный вызов функций, возврат returncode
  • sproc.run(): выполнить подпроцесс до конца, вернуть stdout, stderr, returncode
  • sproc.log(): запустить подпроцесс, распечатать stdout и stderr

Что вы можете узнать из этого кода?

shlex!

shlex - это инструмент для объединения или разделения командных строк. Мне кажется, что через много лет я смог свести мои возни с командными строками к этим нескольким инструкциям.

Использование queue.Queue и маркера конца для разговора между беседами

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

Эта небольшая процедура обслуживания потоков гарантирует, что в любом случае последним в очереди будет конечный маркер, None. Как только два None прибыли на другой конец очереди, процесс завершается.

Уловка с потокобезопасной очередью и маркером конца имеет долгую и успешную историю, так что имейте это в виду.

Документация без дублирования

Это был эксперимент, но он удачный.

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

Я также автоматически записываю README.rst файл документации из исходного кода Python здесь. Опять же, без дублирования, без ошибок - в противном случае всегда сложно поддерживать документацию в актуальном состоянии вместе с кодом.

Спасибо за чтение!

Если вы хотите узнать больше: