Close

28.06.2015

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 модули теперь требуется только

  1. положить bam в папку проекта
  2. положить туда хелпер файл
  3. настроить кое-что для трансляции как показано в статье (пара комманд)
  4. и описать свою трансляцию, например так:
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 🙂

как и что читайте дальше.

списки  рассматриваемых инструментов:

from most powefull to less powerfull

  1. pythonium статья на OpenNet
  2. Brython
  3. https://github.com/PythonJS/PythonJS
  4. https://github.com/daeken/Pyvascript
    1. 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 
  -m MODULE_NAME, --module-name=MODULE_NAME
                        Module name of output
  -i, --list-imports    List import dependencies (without compiling)
  -D, --enable-default  (group) enable DEFAULT options
  -d, --enable-debug    (group) enable DEBUG options
  -O, --enable-speed    (group) enable SPEED options, degrade STRICT
  -S, --enable-strict   (group) enable STRICT options, degrade SPEED
  --enable-wrap-calls   enable call site debugging [False]
  --enable-print-statements
                        enable printing to console [True]
  --enable-check-args   enable function argument validation [False]
  --enable-check-attrs  enable attribute validation [False]
  --enable-accessor-proto
                        enable __get/set/delattr__() accessor protocol [True]
  --enable-bound-methods
                        enable proper method binding [True]
  --enable-descriptor-proto
                        enable __get/set/del__ descriptor protocol [False]
  --enable-track-sources
                        enable tracking original sources [False]
  --enable-track-lines  enable tracking original sources: every line [False]
  --enable-store-sources
                        enable storing original sources in javascript [False]
  --enable-inline-code  enable bool/eq/len inlining [False]
  --enable-operator-funcs
                        enable operators-as-functions [True]
  --enable-number-classes
                        enable float/int/long as classes [False]
  --enable-locals       enable locals() [False]
  --enable-stupid-mode  enable minimalism by relying on javascript-isms
                        [False]
  --use-translator=TRANSLATOR
                        override translator [proto

работает !

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 — он тоже в папке есть, там определяются параметры и настройки и важные для системы глобалы.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *