Welcome to PScript’s documentation!

PScript is a Python to JavaScript compiler, and is also the name of the subset of Python that this compiler supports. It was developed as a part of Flexx (as flexx.pyscript) and is now represented by its own project. Although it is still an important part of Flexx, it can also be useful by itself.

Contents

Getting started

Installation

PScript has no dependencies except Python. It requires Python 2.7 or 3.4+. Use either of these to install PScript:

  • pip install pscript
  • conda install pscript

Basic usage

The main function to use is the py2js function.

A short example:

from pscript import py2js

def foo(a, b=2):
   print(a - b)

print(py2js(foo))

Gives:

var foo;
foo = function flx_foo (a, b) {
   b = (b === undefined) ? 2: b;
   console.log((a - b));
   return null;
};

PScript introduction

The pscript module provides functionality for transpiling Python code to JavaScript.

Quick intro

This is a brief intro for using PScript. For more details see the sections below.

PScript is a tool to write JavaScript using (a subset) of the Python language. All relevant builtins, and the methods of list, dict and str are supported. Not supported are set, slicing with steps, yield and imports. Other than that, most Python code should work as expected … mostly, see caveats below. If you try hard enough the JavaScript may shine through. As a rule of thumb, the code should behave as expected when correct, but error reporting may not be very Pythonic.

The most important functions you need to know about are py2js and evalpy. In principal you do not need knowledge of JavaScript to write PScript code, though it does help in corner cases.

Goals

There is an increase in Python projects that target web technology to handle visualization and user interaction. PScript grew out of a desire to allow writing JavaScript callbacks in Python, to allow user-defined interaction to be flexible, fast, and stand-alone.

This resulted in the following two main goals:

  • To make writing JavaScript easier and less frustrating, by letting people write it with the Python syntax and builtins, and fixing some of JavaScripts quirks.
  • To allow JavaScript snippets to be defined naturally inside a Python program.

Code produced by PScript works standalone. Any (PScript-compatible) Python snippet can be converted to JS; you don’t need another JS library to run it.

PScript can also be used to develop standalone JavaScript (AMD) modules.

PScript is just JavaScript

The purpose of projects like Skulpt or PyJS is to enable full Python support in the browser. This approach will always be plagued by a fundamental limitation: libraries that are not pure Python (like numpy) will not work.

PScript takes a more modest approach; it is a tool that allows one to write JavaScript with a Python syntax. PScript is just JavaScript.

This means that depending on what you want to achieve, you may still need to know a thing or two about how JavaScript works. Further, not all Python code can be converted (e.g. import is not supported), and lists and dicts are really just JavaScript arrays and objects, respectively.

Pythonic

PScript makes writing JS more “Pythonic”. Apart from allowing Python syntax for loops, classes, etc, all relevant Python builtins are supported, as well as the methods of list, dict and str. E.g. you can use print(), range(), L.append(), D.update(), etc.

The empty list and dict evaluate to false (whereas in JS it’s true), and isinstance() just works (whereas JS’ typeof is broken).

Deep comparisons are supported (e.g. for == and in), so you can compare two lists or dicts, or even a structure of nested lists/dicts. Lists can be combined with the plus operator, and lists and strings can be repeated with the multiply (star) operator. Class methods are bound functions.

Caveats

PScript fixes some of JS’s quirks, but it’s still just JavaScript. Here’s a list of things to keep an eye out for. This list is likely incomplete. We recommend familiarizing yourself with JavaScript if you plan to make heavy use of PScript.

  • JavasScript has a concept of null (i.e. None), as well as undefined. Sometimes you may want to use if x is None or x is undefined: ....
  • Accessing an attribute that does not exist will not raise an AttributeError but yield undefined. Though this may change.
  • Keys in a dictionary are implicitly converted to strings.
  • Magic functions on classes (e.g. for operator overloading) do not work.
  • Calling an object that starts with a capital letter is assumed to be a class instantiation (using new): PScript classes must start with a capital letter, and any other callables must not.
  • A function can accept keyword arguments if it has a **kwargs parameter or named arguments after *args. Passing keywords to a function that does not handle keyword arguments might result in confusing errors.
  • Divide by zero results in inf instead of raising ZeroDivisionError.
  • In Python you can do a_list += a_string where each character in the string will be added to the list. In PScript this will convert a_list to a string.

PScript is valid Python

Other than e.g. RapydScript, PScript is valid Python. This allows creating modules that are a mix of real Python and PScript. You can easily write code that runs correctly both as Python and PScript, and raw JavaScript can be included where needed (e.g. for performance).

PScript’s compiler is written in Python. Perhaps PScript can at some point compile itself, so that it becomes possible to define PScript inside HTML documents.

There are things you can do, which you cannot do in Python:

Performance

Because PScript produces relatively bare JavaScript, it is pretty fast. Faster than CPython, and significantly faster than e.g. Brython. Check out examples/app/benchmark.py.

Nevertheless, the overhead to realize the more Pythonic behavior can have a negative impact on performance in tight loops (in comparison to writing the JS by hand). The recommended approach is to write performance critical code in pure JavaScript (using RawJS) if necessary.

Using PSCRIPT_OVERLOAD to increase performance

To improve the performance of critical code, it’s possible to disable some of the overloading that make PScript more Pythonic. This increases the speed of code, but it also makes it more like JavaScript.

To use this feature, write PSCRIPT_OVERLOAD = False. Any code that follows will not be subject to overloading. This parser setting can only be used inside a function and applies only to (the scope of) that function (i.e. not to functions defined inside that function nor any outer scope). If needed, overloading can also be enabled again by writing PSCRIPT_OVERLOAD = True.

Things that are no longer overloaded:

  • The add operator (+), so list concatenation cannot be done with +.
  • The multiply operator (*), so repeating a list or string cannot be done with *.
  • The equals operator (==), so deep comparisons of tuples/lists and dicts does not work.
  • The implicit truthy operator (as e.g. used in an if-statement), so empty tuples/lists and dicts evaluate to True. Note that functions like bool(), all() and any() still use the overloaded truthy.

Support

This is an overview of the language features that PScript supports/lacks.

Not currently supported:

  • import (maybe we should translate an import to require()?)
  • the set class (JS has no set, but we could create one?)
  • slicing with steps (JS does not support this)
  • Generators, i.e. yield (not widely supported in JS)

Supported basics:

  • numbers, strings, lists, dicts (the latter become JS arrays and objects)
  • operations: binary, unary, boolean, power, integer division, in operator
  • comparisons (== -> ==, is -> ===)
  • tuple packing and unpacking
  • basic string formatting
  • slicing with start end end (though not with step)
  • if-statements and single-line if-expressions
  • while-loops and for-loops supporting continue, break, and else-clauses
  • for-loops using range()
  • for-loop over arrays
  • for-loop over dict/object using .keys(), .values() and .items()
  • function calls can have *args
  • function defs can have default arguments and *args
  • function calls/defs can use keyword arguments and **kwargs, but use with care (see caveats).
  • lambda expressions
  • list comprehensions
  • classes, with (single) inheritance, and the use of super()
  • raising and catching exceptions, assertions
  • creation of “modules”
  • globals / nonlocal
  • The with statement (no equivalent in JS)
  • double underscore name mangling

Supported Python conveniences:

  • use of self is translated to this
  • print() becomes console.log() (also supports sep and end)
  • isinstance() Just Works (for primitive types as well as user-defined classes)
  • an empty list or dict evaluates to False as in Python.
  • all Python builtin functions that make sense in JS are supported: isinstance, issubclass, callable, hasattr, getattr, setattr, delattr, print, len, max, min, chr, ord, dict, list, tuple, range, pow, sum, round, int, float, str, bool, abs, divmod, all, any, enumerate, zip, reversed, sorted, filter, map.
  • all methods of list, dict and str are supported (except a few string methods: encode, format_map, isprintable, maketrans).
  • the default return value of a function is None/null instead of undefined.
  • list concatenation using the plus operator, and list/str repeating using the star operator.
  • deep comparisons.
  • class methods are bound functions (i.e. this is fixed to the instance).
  • functions that are defined in another function (a.k.a closures) that do not have self/this as a first argument, are bound the the same instance as the function in which it is defined.

Other functionality

The PScript package provides a few other “utilities” to deal with JS code, such as renaming function/class definitions, and creating JS modules (AMD, UMD, etc.).

PScript API

Convert Python to JavaScript

pscript.py2js(ob=None, new_name=None, **parser_options)

Convert Python to JavaScript.

Parameters:
  • ob (str, module, function, class) – The code, function or class to transpile.
  • new_name (str, optional) – If given, renames the function or class. This can be used to simply change the name and/or add a prefix. It can also be used to turn functions into methods using “MyClass.prototype.foo”. Double-underscore name mangling is taken into account in the process.
  • parser_options – Additional options, see Parser class for details.
Returns:

The JavaScript code as a str object that has a meta attribute with the following fields:

  • filename (str): the name of the file that defines the object.
  • linenr (int): the starting linenr for the object definition.
  • pycode (str): the Python code used to generate the JS.
  • pyhash (str): a hash of the Python code.
  • vars_defined (set): names defined in the toplevel namespace.
  • vars_unknown (set): names used in the code but not defined in it. This includes namespaces, e.g. “foo.some_function”.
  • vars_global (set): names explicitly declared global.
  • std_functions (set): stdlib functions used in this code.
  • std_method (set): stdlib methods used in this code.

Return type:

str

Notes

The Python source code for a class is acquired by name. Therefore one should avoid decorating classes in modules where multiple classes with the same name are defined. This is a consequence of classes not having a corresponding code object (in contrast to functions).

pscript.script2js(filename, namespace=None, target=None, module_type='umd', **parser_options)

Export a .py file to a .js file.

Parameters:
  • filename (str) – the filename of the .py file to transpile.
  • namespace (str) – the namespace for this module. (optional)
  • target (str) – the filename of the resulting .js file. If not given or None, will use the filename, but with a .js extension.
  • module_type (str) – the type of module to produce (if namespace is given), can be ‘hidden’, ‘simple’, ‘amd’, ‘umd’, default ‘umd’.
  • parser_options – additional options for the parser. See Parser class for details.

Evaluate JavaScript or Python in Node

pscript.evaljs(jscode, whitespace=True, print_result=True, extra_nodejs_args=None)

Evaluate JavaScript code in Node.js.

Parameters:
  • jscode (str) – the JavaScript code to evaluate.
  • whitespace (bool) – if whitespace is False, the whitespace is removed from the result. Default True.
  • print_result (bool) – whether to print the result of the evaluation. Default True. If False, larger pieces of code can be evaluated because we can use file-mode.
  • extra_nodejs_args (list) – Extra command line args to pass to nodejs.
Returns:

the last result as a string.

Return type:

str

pscript.evalpy(pycode, whitespace=True)

Evaluate PScript code in Node.js (after translating to JS).

Parameters:
  • pycode (str) – the PScript code to evaluate.
  • whitespace (bool) – if whitespace is False, the whitespace is removed from the result. Default True.
Returns:

the last result as a string.

Return type:

str

More functions

pscript.js_rename(jscode, cur_name, new_name, type=None)

Rename a function or class in a JavaScript code string.

The new name can be prefixed (i.e. have dots in it). Functions can be converted to methods by prefixing with a name that starts with a capital letter (and probably “.prototype”). Double-underscore-mangling is taken into account.

Parameters:
  • jscode (str) – the JavaScript source code
  • cur_name (str) – the current name (must be an identifier, e.g. no dots).
  • new_name (str) – the name to replace the current name with
  • type (str) – the Python object type, can be ‘class’ or ‘def’. If None, the type is inferred from the object name based on PEP8
Returns:

the modified JavaScript source code

Return type:

str

pscript.get_full_std_lib(indent=0)

Get the code for the full PScript standard library.

The given indent specifies how many sets of 4 spaces to prepend. If the full stdlib is made available in JavaScript, multiple snippets of code can be transpiled without inlined stdlib parts by using py2js(..., inline_stdlib=False).

pscript.get_all_std_names()

Get list if function names and methods names in std lib.

pscript.create_js_module(name, code, imports, exports, type='umd')

Wrap the given code in an AMD module.

Note that “use strict” is added to the top of the module body. PScript does not deal with license strings; the caller should do that.

Parameters:
  • name (str) – the name of the module.
  • code (str) – the JS code to wrap.
  • imports (list) – the imports for this module, as string names of the dependencies. Optionally, ‘as’ can be used to make a dependency available under a specific name (e.g. ‘foo.js as foo’).
  • exports (str, list) – the result of this module (i.e. what other modules get when they import this module. Can be a JS expression or a list of names to export.
  • type (str) – the type of module to export, valid values are ‘hidden’, ‘simple’ (save module on root), ‘amd’ , ‘amd-flexx’ and ‘umd’ (case insensitive). Default ‘umd’.

The parser class

Most users probably want to use the above functions, but you can also get closer to the metal by using and/or extending the parser class.

class pscript.Parser(code, pysource=None, indent=0, docstrings=True, inline_stdlib=True)

Parser to convert Python to JavaScript.

Instantiate this class with the Python code. Retrieve the JS code using the dump() method.

In a subclass, you can implement methods called “function_x” or “method_x”, which will then be called during parsing when a function/method with name “x” is encountered. Several methods and functions are already implemented in this way.

While working on ast parsing, this resource is very helpful: https://greentreesnakes.readthedocs.org

Parameters:
  • code (str) – the Python source code.
  • pysource (tuple) – the filename and line number that contain the source.
  • indent (int) – the base indentation level (default 0). One indentation level means 4 spaces.
  • docstrings (bool) – whether docstrings are included in JS (default True).
  • inline_stdlib (bool) – whether the used stdlib functions are inlined (default True). Set to False if the stdlib is already loaded.

Embedding raw JavaScript

PScript allows embedding raw JavaScript using the RawJS class.

class pscript.RawJS(code, _resolve_defining_module=True)

An object to wrap verbatim code to be included in the generated JavaScript. This serves a number of purposes:

  • Using code in PScript that is not valid Python syntax, like regular expressions or the jQuery object $.
  • Write high performance code that avoids Pythonic features like operator overloading.
  • In Flexx’s module system it can be used to create a stub variable in Python that does have a value in JS. This value can imported in other modules, leading to a shared value also in JS.

PScript does not verify the syntax of the code, so write carefully! To allow the features in the 3d point, this object has a magic touch: the __module__ attribute of an instance refers to the module in which it was instantiated, and if it’s a global, its defining name can be obtained.

Example:

# Syntax not usable in Py
myre = RawJS('/ab+c/')

# Code that should only execute on JS
foo = RawJS('require("some.module")')

# Performance
def bar(n):
    res = []
    RawJS('''
        for (var i=0; i<n; i++) {
            if (is_ok_num(i)) {
                res.push(i);
            }
        }
    ''')

Dummy variables

The PScript module has a few dummy constants that can be imported and used in your code to let e.g. pyflakes know that the variable exists. E.g. from pscript import undefined, window Infinity, NaN. Arbitrary dummy variables can be imported using from pscript.stubs import JSON, foo, bar.

Marking a variable as global is also a good approach to tell pyflakes that the variable exists.

PScript user guide

This guide demonstrates the features that PScript supports and how it maps to JavaScript. In most cases you can just read what is not supported and the caveats and start coding.

The basics

Most types just work, common Python names are converted to their JavaScript equivalents.

Slicing and subscriping

String formatting

String formatting is supported in various forms.

Kinds of formatting that is supported:

  • Float, exponential en “general” number formatting.
  • Specifying precision for numbers.
  • Padding of number with “+” or ” “.
  • Repr-formatting.

At the moment, PScript does not support advanced features such as string padding.

Assignments

Declaration of variables is handled automatically. Also support for tuple packing and unpacking (a.k.a. destructuring assignment).

Comparisons

Truthy and Falsy

In JavaScript, an empty array and an empty dict are interpreted as truthy. PScript fixes this, so that you can do if an_array: as usual.

Function calls

As in Python, the default return value of a function is None (i.e. null in JS).

Imports

Imports are not supported syntax in PScript. Imports “from pscript” and “from __future__” are ignored to help writing hybrid Python/JS modules.

PScript does provide functionality to package code in JS modules, but these follow the require pattern.

If statements

Looping

There is support for while loops and for-loops in several forms. Both support continue, break and the else clause.

While loops map well to JS

Explicit iterating over arrays (and strings):

Iterations over dicts:

We can iterate over anything:

Builtin functions intended for iterations are supported too: enumerate, zip, reversed, sorted, filter, map.

Comprehensions

Defining functions

PScript also supports async functions and await syntax. (These map to async and await in JS, which work in about every browser except IE.):

Defining classes

Classes are translated to the JavaScript prototypal class paragigm, which means that they should play well with other JS libraries and e.g. instanceof. Inheritance is supported, but not multiple inheritance. Further, super() works just as in Python 3.

Exceptions

Raised exceptions are translated to a JavaScript Error objects, for which the name attribute is set to the type of the exception being raised. When catching exceptions the name attribute is checked (if its an Error object. You can raise strings or any other kind of object, but you can only catch Error objects.

Globals and nonlocal

Python Builtins

Most builtin functions (that make sense in JS) are automatically translated to JavaScript: isinstance, issubclass, callable, hasattr, getattr, setattr, delattr, print, len, max, min, chr, ord, dict, list, tuple, range, pow, sum, round, int, float, str, bool, abs, divmod, all, any, enumerate, zip, reversed, sorted, filter, map.

Further all methods for list, dict and str are implemented (except str methods: encode, decode, format_map, isprintable, maketrans).

The isinstance function (and friends)

The isinstance() function works for all JS primitive types, but also for user-defined classes.

hasattr, getattr, setattr and delattr

Creating sequences

List methods

Dict methods

Str methods

Using JS specific functionality

When writing PScript inside Python modules, we recommend that where specific JavaScript functionality is used, that the references are prefixed with window. Where window represents the global JS namespace. All global JavaScript objects, functions, and variables automatically become members of the window object. This helps make it clear that the functionality is specific to JS, and also helps static code analysis tools like flake8.

Aside from window, pscript also provides undefined, Inifinity, and NaN.

Release notes

v0.7.6 (09-09-2021)

  • Fix for with-statement when return is used in the context.
  • Early support type hints in the code (by Jennifer Taylor).

v0.7.5 (04-01-2021)

  • Support for Python 3.9.
  • Small docs improvements.

v0.7.4 (11-05-2020)

  • Allow to rename functions starting with an uppercase letter.

v0.7.3 (16-12-2019)

  • Fix CI and Python 3.8 support.

v0.7.2 (16-12-2019)

  • Reflect AST changes in Python 3.8.

v0.7.1 (29-03-2019)

  • Fix the overloaded equals operation to be faster in common cases.

v0.7.0 (08-11-2018)

v0.6.0 (10-08-2018)

  • Support for async and await.
  • Fixes for Python 3.7.
  • Improvements to formatting.

v0.5.5 (12-04-2018)

  • Support for Python 3.7.

v0.5.5 (11-04-2018)

  • Improved string formatting (e.g. '%+3.2f' % 3.1234).
  • String formatting via str.format(...) and f-strings!
  • Dict literals are no longer limited to identifier-valid strings.
  • Fixed a few glitches and small bugs.

v0.5.3 (03-04-2018)

PScript got its own github repo and was renamed from flexx.pyscript to pscript.

Before March 2018

See the Flexx release notes.

Indices and tables