Python in Browser: How to choose between Brython, PyPy.js, Skulpt and Transcrypt?

Here's some info on Brython vs Transcrypt (July 2016, since Transcrypt was added as an option on this question by the OP), gleaned by starting off a project with Brython a few months ago and moving to Transcrypt (completed moving last week). I like Brython and Transcrypt and can see uses for both of them.

For people that are new to this, Brython and Transcrypt both 'transpile' python input to javascript (Edit: maybe it's better to view Brython as a 'a Python implementation for the browser' because it doesn't produce standalone javascript). Both require Python 3 syntax. Brython includes a substantial number of Python standard libraries and some of it's own for dealing with web related things, whereas Transcrypt avoids that for the most part and suggests using Javascript libraries instead.

Brython (Github) can do the conversion in the browser. So you write in python and the brython.js engine converts it to javascript on the fly when the page is loaded. This is really convenient, and is much faster than you might think. However, the brython.js engine that you need to include in your pages is about 500Kb. Also, there's the matter of importing standard libraries, which Brython handles by fetching separate .js files with XHR requests. Some libs are already compiled into brython.js, so not every import will pull in new files, but if you use many imports, things can get slow. However, there are ways around this. What i did was to check the network tab in browser dev tools to see what files were being pulled in when the page was loaded, then delete all the files my project wasn't using in a copy of the Brython src folder, and run the script included with Brython (i think it's at Brython/www/scripts/ that compiles all of the available libs into one file called py_VFS.js that you need to also link to from your html. Normally, it will make one huge 2MB+ file, but if you delete the things you aren't using, it can be quite tiny. Doing it this way, means you only need to pull in brython.js, py_VFS.js and your python code, and no additional XHR requests will be needed.

Transcrypt (Github)on the other hand, is distributed as a python 3 package that you can use manually, or hook into your toolchain, to compile python to javascript in advance. So with Transcrypt, you write in python, run transcrypt against the python and it spits out javascript that you can link to in your project. It is more like a traditional compiler also in that it offers some control over the output. For example, you can choose to compile to ES6 or ES5, or ask it to output sourcemaps (that during debugging let's the browser take you directly to the corresponding python code, insead of the generated javascript code.) Transcrypt's javascript output is pretty terse (or put another way, it's pretty and terse). In my case 150kB of python is converted to 165kB of unminified ES5 javascript. By way of comparison, the Brython version of my project used about 800Kb after conversion.

However, getting the benefits of Transcrypts terseness, requires reading the docs a bit (really just a bit). For example, with Transcrypt, Python's 'truthiness' for data structures like dict, set and list isn't enabled by default and globally enabling it is discouraged because of potential performance issues related to typechecking. For clarity: Under CPython, an empty dict, set or list has truth value False, whereas in Javascript it's considered 'true'.. Example:

myList = []
if myList:    # False in CPython bcs it's empty, true in javascript bcs it exists
    # do some things.

There are at least three ways to address this:

  • Use the -t flag when converting python to javascript e.g.: $ transcrypt -t (not recommended, but probably isn't a problem unless you check for truthiness many times in inner loops of performance sensitive code..)
  • Use __pragma__(tconv) or __pragma__(notconv) within your code to tell the transcrypt compiler to switch on automatic conversion to python-like truth values locally.
  • Instead of checking for the truth value, avoid the problem altogether by just checking len(myList) > 0... Maybe that will be fine for most situations, does the job for my light use.

Right, so my project was getting bigger and i wanted to pre-compile for a performance gain but found it hard to do so with Brython (though it's technically possible, an easy way being to use the online editor and click the javascript button to see the output). I did that and linked to the generated javascript from project.html but it didn't work for some reason. Also, I find it hard to understand error messages from Brython so i didn't know where to start after this step failed. Also, the big size of the outputted code and the size of the brython engine was beginning to bug me. So i decided to have a closer look at Transcrypt, which had at first seemed to be higher grade because i prefer dumbed down instructions that tell me how to get started immediately (these have since been added).

The main thing getting it set up after installing Python3.5 was:

  1. Use venv (it's like a new built-in version of virtualenv that uses less space for each project) to set up a python3.5 project folder (just type: python3.5 -m venv foldername - workaround for ubuntu with package issues for 3.5). This makes 'foldername' with a bin subfolder among other things.
  2. Install Transcrypt python package with pip ('foldername/bin/pip install transcrypt') which installs it to foldername/lib/python3.5/site-packages/transcrypt.
  3. activate the current terminal if you don't want to have to type the full path to foldername/bin/python3.5 every time. Activate by typing: 'source foldername/bin/activate'
  4. Begin writing code and compiling it to javascript for testing. Compile from within the folder you write your code in. For example, i used foldername/www/project. So CD into that folder and run: 'transcrypt -b'. That puts the output in a subfolder called __javascript__. You can then link to the outputted javascript from your html.

Main issues moving across

I have rather simple needs, so your mileage may vary.

  • You need to replace brython or python standard libs with javascript libs. So for example 'import json' is provided by Brython, but under Transcrypt you could use a javascript lib or just use JSON.parse / JSON.stringify directly in your Python code. To include a minified version of a javascript library directly in your python code use this format (note the triple quotes):

    __pragma__ ('js', '{}', '''
    // javascript code
  • Brython's html specific functions don't work with Transcrypt obviously. Just use the normal javascript ways. Examples: 1) under Brython, you might have referred to a specific HTML tag using 'document['id']', but with Transcrypt you'd use 'document.getElementById('id') (which is the same way you do it from javascript). 2) You can't delete a node with 'del nodeName' (bcs that's a brython function). Use something like 'node.parentNode.removeChild(node)'. 3) replace all of brython's DOM functions with the javascript alternatives. e.g. class_name = className; text = textContent; html = innerHTML; parent = parentNode; children = childNodes etc. I guess if you need something that contains alternatives required by some older browsers then there are javascript libraries for that. 4) Brython's set_timeout is replaced with javascripts setTimeout 5) Brython's html tags such as BR() need to be replaced using the normal javascript ways as well as redoing any places you used it's <= dom manipulation syntax. Either inject plain text markup as innerHTML or make the elements using javascript syntax and then attach them using normal javascript DOM syntax. I also noticed that for checkboxes brython uses "if checkbox = 'checked':" but Transcrypt is happy with "if checkbox:"..

  • I finished moving a 2700 line project over last week at which time Transcrypt didn't have support for a few minor things (though they were easy enough to replace with fillers), these were 1) str.lower, str.split (str.split is present, but seems to be the javascript split, which works differently to the python version, the behavior of which i was relying on), 2) round (this seems to be supported in the dev version now) and 3) isinstance didn't work on str, int and float, only on dict, list and set. 4) Another difference from Brython i noticed is that if i pull in a JSON representation of a dict, i need to do so using 'myDict = dict(data)', whereas brython was happy with 'myDict = data'. But that might be related to something in Brython's json.loads, which i replaced directly with JSON.parse. 5) Also without specifically enabled Transcrypts operator overloading (using -o switch for global, or __pragma__('opov') for local), you can't do things like set operations using the overloaded format, but need to use the corresponding functions. E.g.

    a = set([1, 2, 3])
    b = set([3, 4, 5])
    a.difference(b)             # is used instead of a - b
    a.union(b)                  # used instead of a | b
    a.intersection(b)           # used instead of a & b
    a.symmetric_difference(b)   # used instead of a ^ b

6) Also, you can't iterate dicts by default using 'for i in dict:', without enabling that (cmd line -i or __pragma__('iconv'), but you can avoid having to enable it by just using the keys() member e.g.:

for key, value in dict.items():
    # do things for each key and value..

To summarise

  • I like Brython because it's easy to get going with it and to test your code (just F5). It's closer to true python because most of the standard lib is there. I dislike having to include the transpilation engine (Edit: Or one might view it as a python VM) in the browser and the large outputted javascript size. If i had to do things over (but still using Brython), i would have used javascript methods to manipulate the DOM from brython (which you can do..), instead of leaning so much on the brython methods because that wasted time moving across to another transpiler when my needs changed.

  • I like Transcrypt because the outputted javascript is really 'lean and mean' and because the only thing you load browser side is your generated javascript code which is similar in size to your python code. Also because it supports sourcemaps and because it gives me a measure of control over the outputted javascript. And using it taught me quite a bit about optimization.

Hope that helps someone see which of these might be good for their particular project.

Running Python in the Browser is a really good and up-to-date (as of 2019) article that compares Brython, Skulpt, PyPy.js, Transcrypt, Pyodide, Batavia. I highly recommend reading it.

A good summary can be seen in the following pictures.

enter image description here

enter image description here

This page benchmarks the three candidates. Brython emerges as a clear winner.

Despite the 'help' explaining that S.O. is not good for this kind of question, it appears that a concise answer is in this case possible.

Maybe people are being too hasty?