- Установить 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
:(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 -
Пример:
-
- данные в 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
- NULLi
- INTEGERf
- FLOATc
- COMPLEXv
- VECTORh
- HASH-TABLEs
- 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 - это специально созданные каналы ввода-вывода (предполагается, что можно сделать свои каналы и обмениваться данными, чтобы небыло необходимости использовать и подменять стандартные каналы ввода вывода)
- добавить конфигурационный параметр, активирующий tcp/udp/default-pipes/custom-pipes, где:
- Исследовать механизм работы с каналами, избавиться от лишних пересылок данных
- Сделать больше тестов, в частности стресс-тесты, чтобы понять пределы интенсивности обмена данными
- Передавать бинарные данные, а не текстовые (по крайней мере от python-процесса к лисп-системе). Но это потом, после качественной реализации всего остального.
- Может быть, реализовать передачу результатов через общую память (но это тоже "сильно потом")