Transcryptを使って、PythonをJavaScriptに変換する

Cover Image for Transcryptを使って、PythonをJavaScriptに変換する
monotalk
monotalk

PythonのライブラリをJavaScriptに変換したい個人的な要望があり、調べてみると Transcrypt · PyPI というPythonのライブラリで出来そうなことがわかったので、インストールして使ってみた。
目的は冒頭でも書いたが、Pythonをブラウザで動作させたいというよりも、Pythonで作成されたライブラリを、JavaScriptで流用したいになる。
実際に使用した限り、GitHubのissueにもあるが、Pythonのライブラリを変換してJavaScriptで使用するという用途で使用するのは難しい。
オプションが大量にあるので、もしかするとCPythonのimport解決もよしなにやってくれるのかもしれないが、見つけられなかったので知っている方がいたら教えて欲しい。


前提

  • PythonのVersion
    Python 3.7.2
python -V
Python 3.7.2

インストール

pip install Transcrypt

Collecting Transcrypt
  Downloading https://files.pythonhosted.org/packages/48/e4/1e52db523078afef6790d46cc6bea484cd4a99b33ffa4ec62b504ff96ddf/Transcrypt-3.7.16.tar.gz (32.1MB)
    100% |████████████████████████████████| 32.2MB 945kB/s 
Collecting mypy (from Transcrypt)
  Downloading https://files.pythonhosted.org/packages/42/0c/590328b20aa20e03a45ccfa16998d1bfc0af6d0413d52dfa0aca99e0aaba/mypy-0.730-cp37-cp37m-macosx_10_6_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl (16.0MB)
    100% |████████████████████████████████| 16.0MB 1.2MB/s 
Collecting mypy-extensions<0.5.0,>=0.4.0 (from mypy->Transcrypt)
  Downloading https://files.pythonhosted.org/packages/4d/72/8d54e2b296631b9b14961d583e56e90d9d7fba8a240d5ce7f1113cc5e887/mypy_extensions-0.4.1-py2.py3-none-any.whl
Collecting typing-extensions>=3.7.4 (from mypy->Transcrypt)
  Downloading https://files.pythonhosted.org/packages/27/aa/bd1442cfb0224da1b671ab334d3b0a4302e4161ea916e28904ff9618d471/typing_extensions-3.7.4-py3-none-any.whl
Collecting typed-ast<1.5.0,>=1.4.0 (from mypy->Transcrypt)
  Downloading https://files.pythonhosted.org/packages/a0/03/266268b053ad81b8aea17bc3f5e6d3cf074bb4372d229336867a67e17076/typed_ast-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl (215kB)
    100% |████████████████████████████████| 225kB 1.1MB/s 
Installing collected packages: mypy-extensions, typing-extensions, typed-ast, mypy, Transcrypt
  Running setup.py install for Transcrypt ... done
Successfully installed Transcrypt-3.7.16 mypy-0.730 mypy-extensions-0.4.1 typed-ast-1.4.0 typing-extensions-3.7.4

使い方

個人的にドキュメントがわかりにくくて、少し使い方を調べたので記載する。

  • ヘルプ出力
transcrypt -h

でヘルプが出力できる。これでオプションとして何が指定できるか確認できる。

Transcrypt (TM) Python to JavaScript Small Sane Subset Transpiler Version 3.7.16
Copyright (C) Geatec Engineering. License: Apache 2.0


usage: transcrypt [-h] [-a] [-am] [-b] [-c] [-d] [-da] [-dc] [-de] [-dl] [-dm]
                  [-dn] [-ds] [-dt] [-e [ESV]] [-ec] [-f] [-g] [-i] [-jc]
                  [-jk] [-jm] [-k] [-kc] [-l] [-m] [-n] [-o] [-p [PARENT]]
                  [-r] [-s [SYMBOLS]] [-sf] [-t] [-u [UNIT]] [-v] [-x X] [-xr]
                  [-xg] [-xp [XPATH]] [-xt] [-*]
                  [source]

positional arguments:
  source                .py file containing source code of main module

optional arguments:
  -h, --help            show this help message and exit
  -a, --anno            annotate target files that were compiled from Python
                        with source file names and source line numbers
  -am, --alimod         use aliasing for module paths
  -b, --build           rebuild all target files from scratch
  -c, --complex         enable complex number support, locally requires
                        operator overloading
  -d, --docat           enable __doc__ attributes. Apply sparsely, since it
                        will make docstrings part of the generated code
  -da, --dassert        debug: activate assertions
  -dc, --dcheck         debug: perform lightweight consistency check
  -de, --dextex         debug: show extended exception reports
  -dl, --dlog           debug: log compiler messages to disk
  -dm, --dmap           debug: dump human readable source map
  -dn, --dnostrip       debug: no comment stripping of __core__ and
                        __builtin__ in-line modules
  -ds, --dstat          debug: validate static typing using annotations
  -dt, --dtree          debug: dump syntax tree
  -e [ESV], --esv [ESV]
                        ecma script version of generated code, default = 6.
                        The symbol __esv<versionnr>__ is added to the global
                        symbol list, e.g. __esv7__.
  -ec, --ecom           enable executable comments, seen as comments by
                        CPython but as executable statements by Transcrypt
  -f, --fcall           enable fastcall mechanism by default. You can also use
                        __pragma__ ('fcal') and __pragma__ ('nofcall')
  -g, --gen             enable generators and iterators. Disadvised, since it
                        will result in a function call for each loop
                        iteration. Preferably use __pragma__ ('gen') and
                        __pragma__ ('nogen')
  -i, --iconv           enable automatic conversion to iterable by default.
                        Disadvised, since it will result in a type check for
                        each for-loop. Preferably use __pragma__ ('iconv') and
                        __pragma__ ('noiconv') to enable automatic conversion
                        locally
  -jc, --jscall         enable native JavaScript calls for Python methods.
                        This is fast, but doesn't support bound method
                        assignment, decorators and non-instance methods.
                        Preferably use __pragma__ ('jscall') and __pragma__
                        ('nojscall') to enable native JavaScript calls locally
  -jk, --jskeys         interpret {key: 'value'} as {'key': 'value'} and
                        forbid {key (): 'value'}, as JavaScript does.
                        Disadvised, since it's less flexible than the Python
                        interpretation. Either follow Python semantics by
                        using {'key': 'value'} explicitly if you want literal
                        keys or use __pragma__ ('jskeys') and __pragma__
                        ('nojskeys') locally instead to make clear local
                        deviation from Python semantics
  -jm, --jsmod          give % and %= JavaScript rather than Python behaviour.
                        Disadvised, since it deviates from the mathematical
                        'modulo' operator. Either follow Python semantics or
                        use __pragma__ ('jsmod') and __pragma__ ('nojsmod')
                        locally instead to make clear local deviation.
  -k, --kwargs          enable keyword arguments by default. In general this
                        is disadvised, use __pragma__ ('kwargs') and
                        __pragma__('nokwargs') locally instead to prevent
                        bloated code
  -kc, --keycheck       enable checking for existence of dictionary keys. In
                        general this is disadvised, use __pragma__
                        ('keycheck') and __pragma__('nokeycheck') locally
                        instead to prevent bloated code
  -l, --license         show license
  -m, --map             generate source map
  -n, --nomin           no minification
  -o, --opov            enable operator overloading by default. In general
                        this is disadvised, use __pragma__ ('opov') and
                        __pragma__('noopov') locally instead to prevent slow
                        code
  -p [PARENT], --parent [PARENT]
                        object that will hold application, default is window.
                        Use -p .none to generate orphan application, e.g. for
                        use in node.js
  -r, --run             run source file rather than compiling it
  -s [SYMBOLS], --symbols [SYMBOLS]
                        names, joined by $, separately passed to main module
                        in __symbols__ variable
  -sf, --sform          enable support for string formatting mini language
  -t, --tconv           enable automatic conversion to truth value by default.
                        Disadvised, since it will result in a conversion for
                        each boolean. Preferably use __pragma__ ('tconv') and
                        __pragma__ ('notconv') to enable automatic conversion
                        locally
  -u [UNIT], --unit [UNIT]
                        compile to units rather than to monolithic
                        application. Use -u .auto to autogenerate dynamically
                        loadable native JavaScript modules, one per Python
                        module. Use -u .run to generate the loader and the
                        staticcally loadable runtime unit. Use -u .com to
                        generate a statically loadable component unit.
  -v, --verbose         show all messages
  -x X, --x X           reserved for extended options
  -xr, --xreex          re-export all imported names
  -xg, --xglobs         allow use of the 'globals' function
  -xp [XPATH], --xpath [XPATH]
                        additional module search paths, joined by $, #'s will
                        be replaced by spaces
  -xt, --xtiny          generate tiny version of runtime, a.o. lacking support
                        for implicit and explicit operator overloading. Use
                        only if generated code can be validated, since it will
                        introduce semantic alterations in edge cases
  -*, --star            Like it? Grow it! Go to GitHub and then click [* Star]

Ready

1. Transcrypt: what and why — Transcrypt 3.7.16 documentation
に記載されているプログラムを変換する。

transcrypt classes.py      

を実行すると、カレントディレクトリに、__target__ というディレクトリが作成されその中に、JavaScriptが生成される。


six の import に失敗する

sixを使用しているPythonスクリプトを変換しようとしたところ、以下のエラーが発生した。

Error while compiling (offending file last):
        File 'allpairs', line 10, namely:

        Import error, can't find any of:
                /usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/six/moves.py
                /usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/six/moves.js
                /usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload/six/moves.py
                /usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload/six/moves.js

sixのmovesというモジュールのimportに失敗している。
Six: Python 2 と 3 の互換性ライブラリ — six 1.9.0 ドキュメント を引用する。

Python 3 は標準ライブラリの再編成を行い、いくつかの機能は異なるモジュールに移動しました。 Six は six.moves なるフェイクを介すことで、それらに一貫したインターフェイスを提供しています。例えば、HTML を解析するモジュールをロードするために、このようにします:

Pythonではフェイクモジュールを作成できる。おそらくこの形式のモジュールがあると、importに失敗するような動きをしている。
Use a Python class as a fake module     


CPython モジュールの import に失敗する。

sixがimport出来ないので、対象ライブラリをPython3文法で書き直し、再度変換をかけたところ以下のエラーが発生した。

Error while compiling (offending file last):
       File '/allpairspy/__init__.py', line 11, at import of:
       File 'allpairspy/allpairs.py', line 5, at import of:
       File 'functools', line 246, namely:

       Can't import module 'functools'

Aborted

functoolscpython/Lib at master · python/cpython のモジュールで以下のIssue にCPythonのモジュールはあえて含めていない旨が記載されている。

関数などで、ソース移植で対応できるものもあるが、cpython/init.py at master · python/cpython のOrderedDictの移植が必要になり、これは無理だと思って諦めた。