Skip to content

Latest commit

 

History

History
171 lines (157 loc) · 14.3 KB

Readme.ru.md

File metadata and controls

171 lines (157 loc) · 14.3 KB

CL-TO-PYTHON - common lisp библиотека для взаимодействия с Python

Загрузка в лисп-систему:

Первый способ:

  • Установить quicklisp: https://www.quicklisp.org/beta/
  • (load "<путь_к_cl-to-python>/load.lisp")

Второй способ:

  • Установить quicklisp: https://www.quicklisp.org/beta/
  • Добавить путь к системе в ASDF:
    • (push #P"<<путь_к_cl-to-python>/" asdf:*central-registry*)
  • Загрузить через Quicklisp: (ql:quickload :cl-to-python)

Запуск тестов (после загрузки в лисп-систему)

  • (asdf:test-system :cl-to-python)

Использование (после входа в пакет cl-to-python или после импорта этого пакета)

  • Сделать активным пакет cl-to-python: (in-package #:cl-to-python) (или импортировать cl-to-python в свой пакет, см. defpackage)
  • Запуск python-процесса: (py-start)
  • Остановка python-процесса: (py-stop)
  • Динамическая переменная *python* после (py-start) будет содержать экземпляр структуры PYTHON-CONN, инкапсулирующую сущности необходимые для взаимодействия с python-процессом Eсли требуется запускать несколько python-процессов, эту переменную необходимо перекрыть, например через форму let: (let ((*python* nil)) (py-start) ...)
  • Выполнение python-кода: (py-eval "1 + 2") => 3
  • Выполнение python-определений:
    • (py-exec "tmp = 1 + 2") => None
    • (py-eval "tmp") => 3
  • Выполнение python-кода в отдельном потоке:
    • (py-peval "print(1 + 2)")
      => "Thread-2"
      => #<PYTHON-OUTPUT "3
      ">

Требования

  • В данный момент протестировано в:
    • ОС: windows-10
    • Лисп: "SBCL 2.2.9" (на более ранней >= 2.0.0 - скорее всего, также будет работать)

Итоговые требования

  • Планируется достичь работоспособности также в популярных linux-системах, например в Ubuntu 20.04/22.04
  • Также необходимо обеспечить работу в lispworks (для этого надо обеспечить поддержку в lispworks uiop:launch-program)

Примечания к реализации

  • Текущая реализация далека от идеала, требуется рефакторинг, пересмотр решений и прочее. Но при этом, реализация рабочая, тесты для систем в разделе требования - проходят, поэтому при развитии системы, следует отталкиваться от текущей реализации, улучшая понемногу и проверяя, что тесты продолжают работать
  • Одна из целей, наряду с возможной реализацией на базе tcp/udp, сохранить возможность обмена данными через каналы

Инструкции по работе с репозиторием

  • Перед коммитами, push-ами, крайне желательно проверять что сохранилась работоспособность системы, через запуск тестов (asdf:test-system :cl-to-python)

Детали реализации, проблемы

  • Назначение файлов на данный момент:
    • parsers.lisp - здесь перевод результатов из строк в лисп-типы
    • cl-to-python.lisp - основная логика, в данный момент здесь
    • my2.py (будет переименован) - скрипт поддержки системы на стороне python-процесса
    • ... другие файлы штатного назначения
  • Принцип работы:
    • после запуска python-процесса ф-ией py-start - стартует python-процесс выполняя скрипт my2.py
    • в my2.py выполняется бесконечный цикл, который читает данные из потока ввода и их интерпретирует (также в python'e стартует дополнительный поток, проверяющий что родитель python-процесса консоль ещё жива, когда процесс консоли завершиться - завершиться python-процесс)
    • в лисп-системе запускается отдельный поток, которые вычитает результаты из потока, соответствующего потоку вывода в python-процессе и результаты складывает в (results-hash) либо (outputs-hash) если это вывод выполненного python-кода (этот поток выполнения можно найти выполнив (python-conn-worker *python*))
    • после отправки кода в python-процесс, запускается бесконечный цикл ожидающий результата, на каждой итерации которого, отправляется "пустая команда", которая должна заставить Python прочитать очередную порцию данных (почему это необходимо - не совсем понятно, однако без этого Python застывает в бесконечном ожидании, может быть это связано с буфферизованным вводом-выводом, однако на стороне лисп-системы сбрасывается буфер через finish-output и это не помогает, запуск python'a с ключом -u - тоже не помогает)
  • Форматы пересылаемых данных:
    • данные в python передаются в следующем формате:
      <OP><LENGTH>\n[<UUID>]<PYTHON-CODE>
      где:
      • <OP> - тип операции

        • e - выражение, передаётся при вызове (py-eval ...)
        • x - определение, передаётся при вызове (py-exec ...)
        • p - выражение для выполнения в отдельном потоке, передаётся при вызове (py-peval ...)
        • f - команда python-процессу завершить бесконечный цикл ожидания команд и завершиться (мягкий выход)
      • <LENGTH> - длина сообщения ([<UUID>] + <PYTHON_CODE>)

      • <UUID> - уникальный идентификатор команды

      • <PYTHON-CODE> - выражение или определение на языке Python

      • Пример:

e43
[0888CFA2-C79E-4408-96BE-1D5EAFC907ED]1 + 2    
  • после выполнения кода в python-процессе возвращается результат в виде:
    <RETTYPE><LENGTH>\n[<UUID>]<RESTYPE><RES>
    где:
    • <RETTYPE> - тип возвращаемого сообщения, пока это только r

    • <LENGTH> - длина сообщения ([<UUID>] + <RESTYPE> + <RES>)

    • <UUID> - уникальный идентификатор команды

    • <RESTYPE> - тип результата:

      • e - ошибка
      • o - вывод полученный после выполнения python-кода
      • _ - необрабатываемый python-тип,
      • b - логический тип
      • n - NULL
      • i - INTEGER
      • f - FLOAT
      • c - COMPLEX
      • v - VECTOR
      • h - HASH-TABLE
      • s - STRING

      (не все значения типов на данный момент, корректно обрабатываются в лисп-системе, сейчас обрабатываются: булевы значения, строки, целые числа и частично массивы)

      Для анализа соответствий типов Python типам в лисп-системе, см. словари в my2.py: python_to_lisp_type и lisp_type_to_sym

    • Пример:

r40
[0888CFA2-C79E-4408-96BE-1D5EAFC907ED]i3    

  • Сейчас то, что выводит питон-код в поток вывода - считывается через подмену потока вывода, это надо как-то менять, в частности из-за того, что это не будет корректно работать при одновременном использовании вычислений в отдельном потоке
  • Результаты вычисления и произведённый вывод сейчас отсылаются в лисп-процесс отдельно - необходимо результат и вывод отсылать в лисп-процесс в одном сообщении
  • По какой-то причине, передать данные в канал и просто ожидать что python-процесс их прочтёт и будет продолжать обработку - не получается, так как python-процесс продолжает висеть в ожидании данных (тоже самое наблюдалось и в обратном случае, в ожидании данных лисп-процессом). Это решается передачей дополнительных данных. Этот момент требует исследования и как минимум выявления того, как именно и сколько надо передавать дополнительных данных.
  • Ф-ия (py-stop) приводит к завершению процесса консоли, запускающего python-процесс, таким образом, python остаётся висеть в памяти. Для того, чтобы он уничтожался после уничтожения родителя, в python-скрипте поддержки работы системы (сейчас my2.py, будет переименован) запускается поток проверяющий существование родителя - если родитель перестал существовать, он уничтожает python-процесс. Это нужно сделать как-то по-другому. Пока не понятно как. Может быть как-то передать в лисп-процесс, PID python-процесса. Может быть, повесить в python-e обработчик сигнала, который должен передаваться, после завершения родителя. В каких-то кейсах текущей реализации - python-процесс всё равно не уничтожается и продолжает "висеть" в памяти (наверное тогда, когда лишившись "родителя" он застыл в ожидании сообщения)

Планы развития

  • Усовершенствовать систему логирования
  • В структуру PYTHON-CONN добавить все сущности связанные с взаимодействием с python-процессом
  • Рефакторинг (в частности, разбить функционал из cl-to-python.lisp на несколько файлов)
    • документирование, создание схем взаимодействия
    • оптимизация
  • Выполнение python-определений в отдельном потоке - сделать ф-ию py-pexec (аналогично существующей ф-ии py-peval)
  • Сделать также реализацию на базе сокетов (tcp/udp)
  • Сохранить реализацию через каналы
    • добавить конфигурационный параметр, активирующий tcp/udp/default-pipes/custom-pipes, где:
      • default-pipes - это стандартные каналы ввода вывода
      • custom-pipes - это специально созданные каналы ввода-вывода (предполагается, что можно сделать свои каналы и обмениваться данными, чтобы небыло необходимости использовать и подменять стандартные каналы ввода вывода)
  • Исследовать механизм работы с каналами, избавиться от лишних пересылок данных
  • Сделать больше тестов, в частности стресс-тесты, чтобы понять пределы интенсивности обмена данными
  • Передавать бинарные данные, а не текстовые (по крайней мере от python-процесса к лисп-системе). Но это потом, после качественной реализации всего остального.
  • Может быть, реализовать передачу результатов через общую память (но это тоже "сильно потом")