Python to JS translation
Is the Python to JS translataion are Crazy ??? ну я и решил попробовать.
у нас был код на питоне, но позже мы поняли, что хотим запускать его на Client Side, переписывать — не хотелось, ведь все оттестировано, да и 21 век уже…
Беглым поиском было найдено как минимум с десяток способов и это веяло уверенностью. НО
в итоге пройден собственный путь и выбраны на практическом опыте работающие и «хорошие» способы.
в конце удалось достичь красивой сборки, и быстрой/приемлимой скорости работы питона на JS стороне.
loadfile('pyjshelper.lua')('TASK', 'INIT') pyjs.compile( "py_moduleA" , nil) pyjs.compile( "py_moduleB" , 'py_moduleA', 'json') pyjs.InitAllModules('js/py_init_module.js', false)
что удалось достич
теперь чтобы странслировать Python проект в JS модули теперь требуется только
- положить bam в папку проекта
- положить туда хелпер файл
- настроить кое-что для трансляции как показано в статье (пара комманд)
- и описать свою трансляцию, например так:
loadfile('pyjshelper.lua')('TASK', 'INIT') pyjs.compile( "py_moduleA" , nil) pyjs.compile( "py_moduleB" , 'py_moduleA', 'json') pyjs.InitAllModules('js/py_init_module.js', false)
и запустить сборку:
$ ./bam
готово, но чтобы прийти к этому надо было «нарвать мешок волос с …» no comments 🙂
как и что читайте дальше.
списки рассматриваемых инструментов:
- http://stromberg.dnsalias.org/~strombrg/pybrowser/python-browser.html
- https://wiki.python.org/moin/WebBrowserProgramming
from most powefull to less powerfull
- pythonium статья на OpenNet
- Brython
- https://github.com/PythonJS/PythonJS
- https://github.com/daeken/Pyvascript
- the rapydscript are wrapper : https://bitbucket.org/pyjeon/rapydscript
ближе к JS
Какую задачу я решаю
— есть наша библиотека на Python, с классами и функциями, тестами и т. д.
— я хочу странслировать ее в JS (для браузера) так, чтобы прямо из JS кода можно было использовать (примерно как c emscripten) с какими-то врапперами или без них. хочу CJS модули или AMD — пофиг, но модули.
— хочу странслировать на уровне языка и его магии, а не на уровне байт кода, не хочу эмулировать весь питон с потрохами — нужна скорость.
— мне не нужен доступ к DOM из Python, если только я его сам не сделаю. нужна только голая JS либа и слой эмуляции OS который есть в питоне.
— нужно максимально легковесное преобразование но с import базовыми модулями и возможно слоем эмуляции ОС.
Общий (пред)вывод после всех экспериментов:
есть два вида инструментов такого рода: 1) работают через PY_CODE-AST-JS_CODE 2) PY_CODE-BYTECODE-JS_CODE
другие два вида: есть компиляторы и есть интерпретаторы. (нам конечно интересны компиляторы, т.к. если возможно компилировать — зачем тогда интерпретировать? )
Каждый подвид представлен каким-то образом: набором решений, но среди них есть разные!!! все только с первого взгляда кажется нормальным, реально — барахло, по большей части.
Либо инструмент = фуфло (не дописан, узок набор возможностей, бажный, не устраивает по категории/виду)
либо это техно-монстр который зашкаливает так, что вам он не нужен.
вот я с этим и столкнулся.
критика
https://github.com/rusthon/Rusthon/tree/master/pythonjs
при конвертации выдает стектрейс ПРО СЕБЯ, а НЕ ПРО МОЙ КОД. иногда даже на простых примерах это спутывает.
http://www.skulpt.org/
не устроил тем, что конвертирует python в JS ПРИ КАЖДОЙ ЗАГРУЗКЕ СТРАНИЦЫ. это значит что нам придется открыть исходник.
это мы всегда успеем
http://brython.info/
очень сложный инструмент. предназначен так же для обработки исходного Python в браузере.
не нашел нормального туториала как им сконвертировать код в JS при разработке. поэтому отпадает.
py2js https://github.com/mattpap/py2js
пишет ошибки вида (про себя, не про мой код):
pavlov@pavlov-pc:~/SATEK/sup/branches/dev3/mmdb_js/py2js$ python2.7 py2js.py ../t1.py Traceback (most recent call last): File "py2js.py", line 836, in main() File "py2js.py", line 828, in main js = convert_py2js(s) File "py2js.py", line 736, in convert_py2js return "\n".join(v.visit(t)) File "py2js.py", line 214, in visit return visitor(node) File "py2js.py", line 223, in visit_Module module.extend(self.visit(stmt)) File "py2js.py", line 214, in visit return visitor(node) File "py2js.py", line 326, in visit_ClassDef assert len(bases) >= 1 AssertionError pavlov@pavlov-pc:~/SATEK/sup/branches/dev3/mmdb_js/py2js$ cat ../t1.py class A: def __init__(self, x,y,z): self.x = x self.y = y self.z = z def foo(self, w): return self.x + w pavlov@pavlov-pc:~/SATEK/sup/branche
и не поддерживает Import. — в топку
https://github.com/chrivers/pyjaco
пробовал его. выполнил их титульный README, и то были проблемы когда делал даже тупо по их описаниям…. — не пашет.
требует Pyjimas какой-то (как я позже понял эта хрень нужна чтобы работать с DOM, мне это не надо , но а как без нее запустить — не понятно).
тесты не прошли совсем. Nodejs не мог найти функцию load
. у них в описании ничего про ноду не сказано и как ее настроить — ничего. (может быть версия не та, но это разве моя проблема? — должны были написать)
я посчитал, что это не нормально и оставил проект.
(надо сказать, что какие-то конвертации все же эта штука выполняет)
PYJS https://github.com/pyjs/pyjs
кажется очень солидным. хотя туториал — фуфло, т.е. бесполезен. пришлось лезть даже в исходники чтобы разобрать как этим пользоваться.
также нашел некоторые статейки про него
- http://www.mccarroll.net/py2js/
- https://github.com/mattpap/py2js/blob/master/doc/src/py2js.rst
почему pyjs лучше? логика такая: смотрим на проект видим папку pyjs и другие папки. что это за другие папки ? это всякие компоненты и системы написанные с использованием pyjs, которые автор сам нахреначил на базе своей либы. значит она точно работает и большинство багов пофикшены. кроме того в недрах этих папок можно найти примеры использования и тайные знания.
туториал бы не помешал — но в этом проекта он никакой, и видимо так и будет никаким.
PYJS бывает разный. есть много форков! (а как же?) расстраивает, что документы и статьи про него не могут помочь — в проекте даже структура иная и файлов упомянутых уж нигде нет.
кое какое описание есть тут
http://pyjs.org/Overview.html
чтобы собрать проект надо писать сборочный скрипт !
ужасно что: проект завязан накрепко с каким-то pyjamas, который не понятно откуда брать, и вообще он мне не нужен , видно же что магия там какая-то с браузерами… будем это обходить. Проект должен рашать 1 (одну) проблему. и документация должна быть об этом. рррр.
про транслятор сказано тут
The pyjs Python-to-JavaScript compiler is actually a language translator. It has two main modes: -O and —strict. The —strict mode aims for full Python interoperability, even at the expense of performance, whilst the -O mode is, like gcc’s -O option, «optimised» for speed, even at the expense of missing out certain Python language features and relying on JavaScript instead. The Migration Guide best describes the differences.
pavlov@pavlov-pc:~/SATEK/sup/branches/dev3/mmdb_js/pyjs$ python2.7 ./pyjs/translator.py --help Usage: usage: translator.py [options] file... Options: -h, --help show this help message and exit -o OUTPUT, --output=OUTPUT Place the output into
работает !
python2.7 pyjs/translator.py ../map_db.py > ../map_db.js # НАДО ВАЩЕ sudo cp -r ./pyjswidgets/pyjamas/ /usr/local/lib/python2.7/dist-packages/pyjamas sudo cp -r ./pyjs /usr/local/lib/python2.7/dist-packages/pyjs
тут обсуждают что лучше PYJACO или PYJS.
— https://groups.google.com/forum/#!topic/pyjaco/8LIORhPrmQ8
— http://pypyjs.org/
жуть. pypy на JS (asm.js). грузится долго, потому что большой. (откройте ссылку и сами посмотрите), это уже изврат, я считаю, мне точно не подходит.
то, что пригодилось
PYJS оказался тем самым общим знаменателем, НО, тут я ругану его как следует.
PYJS — написан , .. как бы это помягче…, муда… IT хулиганом, КОД УЖАСЕН.
видно, что это очень таллантливый человек, но то КАК ОН ПИШЕТ это безобразие, он даже мою видавшую виду программисткую психику разбалансировал до жестких матов вслух.
технически мне помешало в этом проекте:
— ужасная документация, которой и нет и она не соответствует либо устарела, либо вобще от другого проекта — та, что есть только запутывает
— код написан на антипатернах, читать его — боль, использовать — тоже. костыль на костыле, все так выглядит и в отладке и с виду.
— система сборки и исполнения — полное фуфло. только представьте: итоговый код в итоге попадает в другой фрейм, о как. а еще всё хранится в одной распухшей глобальной переменой (JS Object). поля этого объекта можно воспринимать как просто глобальные переменные, так вот, они используются в каждом модуле и в каждом модуле перед работой инициализируются своим смыслом перед работой, порядок инициализации модулей я деже описывать не буду, чел как специально издевается….
Хорошее:
— код транслируется по честному, без этапа байткода, достоточно компактно и он работает, оверхед не большой и приемлимый
— в этом честном режиме поддерживаются модули, import
— есть большая часть полезных системных модулей (которых мне хватает)
Пришлось написать свой скрипт сборки чтобы конвертация шла на УРА!
и работала в моем фрейме, а не в каких-то дочерних/служебных, которые мне, вообще, только мешают.
чтобы пользоваться любой подобной системой «надо разобраться», — значит надо автоматизировать, подумал я, чтобы это стало простой технологичекой операцией.
вот мои файлы, файлы библиотеки которую надо перенести в JS
drwxrwxr-x 7 pavlov pavlov 4096 июня 27 12:09 ../ -rw-rw-r-- 1 pavlov pavlov 4710 июня 27 12:09 db_convert.py -rw-rw-r-- 1 pavlov pavlov 226 июня 27 12:09 db_xml_export.py -rw-rw-r-- 1 pavlov pavlov 86 июня 27 12:09 db_xml_import.py -rw-rw-r-- 1 pavlov pavlov 5200 июня 27 12:09 db_xml.py -rw-rw-r-- 1 pavlov pavlov 1871 июня 27 12:09 js_tc.py -rw-rw-r-- 1 pavlov pavlov 20214 июня 27 12:09 map_db.py -rw-rw-r-- 1 pavlov pavlov 21186 июня 27 12:09 mindmap.py -rw-rw-r-- 1 pavlov pavlov 369 июня 27 12:09 mmexceptions.py -rw-rw-r-- 1 pavlov pavlov 6238 июня 27 12:09 mmservice.py -rw-rw-r-- 1 pavlov pavlov 14466 июня 27 12:09 node.py -rw-rw-r-- 1 pavlov pavlov 37129 июня 27 12:09 planarization.py -rw-rw-r-- 1 pavlov pavlov 11671 июня 27 12:09 test_blackbox.py -rw-rw-r-- 1 pavlov pavlov 4332 июня 27 12:09 test_hierarchy.py -rw-rw-r-- 1 pavlov pavlov 1752 июня 27 12:09 test_performance.py -rw-rw-r-- 1 pavlov pavlov 14228 июня 27 12:09 test_visual.py -rw-rw-r-- 1 pavlov pavlov 6968 июня 27 12:09 tree_drawer.py -rw-rw-r-- 1 pavlov pavlov 15074 июня 27 12:09 users.py -rw-rw-r-- 1 pavlov pavlov 376 июня 27 12:09 vtest.py
чтобы работать с pyjs надо сделать python2 виртуальоне окружение, включить его и установить его туда, иначе все — ничего не выйдет. у меня это все конфликтовало с кучей либ, и иногда виделась не верная версия питона.
$ virtualenv -p /usr/bin/python2.7 venv/ $ source venv/bin/activate $ pip install git+https://github.com/pyjs/pyjs.git#egg=pyjs # может не сработать, поэтому надо сделать: $ git clone https://github.com/pyjs/pyjs.git $ cd $ python setup.py install
у вас должны появится команды :
pyjscompile pyjsbuild pyjampiler
иначе облом.
«bam» — сборка с удовольствием
эту систему сборки я использовал для облегчения , она очень гибкая и маленькая и простая.
нужно было всего-то написать к нему нормальный хелпер для сборки своего проекта, Этот файл подойдет и для трансляции ваших Python проектов в JS, простое описание — и он все построит.
я выложу последние файлы сборщика себе в папку на gdrive (ссылка)
вы просто качаете себе ./bam.exe для вашей OS, скачиваете в папку c проектом файлы с моей папки на gdrive (js build helper) и пишете такой красивый и понятный скрипт сборки вашего проекта bam.lua в папке проекта, по образцу: (у меня он такой):
-- грузим наш хелпер ОБЯЗАТЕЛЬНО В НАЧАЛЕ loadfile('pyjshelper.lua')('TASK', 'INIT') -- TODO перенести в хелпер -- тут мы описываем зависимости между JS модулями "системными" -- все это стандартно и зависит от "чудо генератора pyjs", определено опытным путем pyjs.JsDep('sys', nil) pyjs.JsDep('stat', nil) pyjs.JsDep('pyjspath', 'sys', 'stat', 'genericpath') -- pyjspath зависит от 'sys', 'stat', 'genericpath' pyjs.JsDep('os.path', 'pyjspath') pyjs.JsDep("os", "os.path") pyjs.JsDep("math", nil) pyjs.JsDep("time", "math") pyjs.JsDep("_random", nil) pyjs.JsDep("binascii", nil) pyjs.JsDep("random", "math", "os", "binascii", "_random", "time") pyjs.JsDep("json", nil) pyjs.JsDep("pyjslib", 'sys') -- не уверен что только это -- собираем НАШИ модули, указывая НАШИ зависимости (+ JS зависимости по JS модулям, и системным тоже) -- pyjs модули делать не умеет, это наш хелпер модифицирует код сам. pyjs.compile( "js_tc" , nil) pyjs.compile( "mmexceptions", nil ) -- например, собираем map_db, который использует 'js_tc', 'random', 'os' pyjs.compile( "map_db", 'js_tc', 'random', 'os' ) pyjs.compile( "mmservice", nil ) pyjs.compile( "users", 'json', 'map_db', 'mmexceptions', 'mmservice', 'planarization' ) pyjs.compile( "node", 'mmexceptions', 'json', 'map_db', 'users' ) pyjs.compile( "planarization", 'json', 'map_db' ) pyjs.compile( "mindmap", 'mmexceptions', 'map_db', 'node', 'users', 'planarization', 'mmservice' ) -- теперь у нас есть все define модули для requirejs и есть инфа кто от кого зависит -- запишем скрипт инициализации питоновских модулей, это в конце, без этого нельзя -- если в конце false - сами инициализируем только последний модуль -- а он уже остальные -- если true - инициализируем сами в правильном порядке все модули pyjs.InitAllModules('out/lib/final2.js', false)
запускаете ./bam и
появится папка output и в ней будут всякие промежуточные результаты. в том числе и МОДУЛИ JS.
и получаете модуль для инициализации всех иных загруженных модулей final2.js типа такого
require([ "output/lib/js_tc", "output/lib/mmexceptions", "output/lib/map_db", "output/lib/mmservice", "output/lib/users", "output/lib/node", "output/lib/planarization", "output/lib/mindmap", "output/lib/pyjslib" ], function(pyjslib){ if( $pyjs['loaded_modules']['pyjslib']){ pyjslib=$pyjs['loaded_modules']['pyjslib']; // make globals for init modules $p = $pyjs['loaded_modules']['pyjslib']; }; $pyjs['loaded_modules']['js_tc'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['mmexceptions'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['map_db'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['mmservice'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['users'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['node'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['planarization'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['mindmap'].unicode=function(str, coding){return str}; $pyjs['loaded_modules']['pyjslib'](); $pyjs['loaded_modules']['js_tc'](); $pyjs['loaded_modules']['mmexceptions'](); $pyjs['loaded_modules']['map_db'](); $pyjs['loaded_modules']['mmservice'](); $pyjs['loaded_modules']['users'](); $pyjs['loaded_modules']['node'](); $pyjs['loaded_modules']['planarization'](); $pyjs['loaded_modules']['mindmap'](); });
Это генерируются костыли, без которых вообще ничего не загрузится и не заработает. пришлось частично заменить функции pyjs bootstrap.js, который все делает через …. , да, все делает, но это ужас, что.
для продакшена я модули и инициализацию упаковываю дополнительно и минимизирую так:
$ cat ./pack.sh node r.js -o name=output/lib/final2 out=main-built.js baseUrl=.
И получаю минифицированный main-built.js. который работает как надо.
чтобы сделать питоновскую строку из обычной надо:
s=$pyjs.loaded_modules.builtins.str('abcd')
у питона строка это PY объект со своими методами…
а так можно сделать из JS свой Python объект класса MindMap
mm = $pyjs.loaded_modules.mindmap.MindMap($pyjs.loaded_modules.builtins.str('m1'), $pyjs.loaded_modules.builtins.str('u1') )
в итоге жить можно.
ах, да, забыл: надо в header включить еще скрипт pyjs_init.js — он тоже в папке есть, там определяются параметры и настройки и важные для системы глобалы.