The post Running Python in the Browser with Web Assembly first appeared on Qvault.
We’ve been wanting to expand Qvault’s course curriculum, and one of the most requested programming languages has been Python. Because our courses allow students to write and execute code right in the web browser, we decided to look into existing projects that allow a Python interpreter to run in the browser using Web Assembly. We settled on a tool called Pyodide, which does just that.
To see it in action, check out the finished product, a Python playground.
What is Pyodide?
Pyodide is an open-source project that comprises a Python interpreter that has been compiled to Web Assembly.
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
In other words, normally only JavaScript can run in a browser, but if you can compile your source code to Wasm, then you can run any programming language in the browser. (At the time of writing we run Python, Rust, and Go this way on our playground and in our courses)
Pyodide brings the Python 3.8 runtime to the browser via WebAssembly, along with the Python scientific stack including NumPy, Pandas, Matplotlib, parts of SciPy, and NetworkX. The
packages
directory lists over 35 packages which are currently available.
How Did We Do It?
Our Python execution plan is quite similar to the way we run Go code in the browser. There are basically three steps:
- Write a worker file that defines how code is executed
- Write a worker helper that abstracts the details of spinning up, communicating, and terminating workers
- Implement the helper in the view so that users can execute code and see the code’s output
If you want to know how that all works please read this article about Web Workers and WASM in Go before continuing.
If you have finished that first article on Web Workers, then all you will need to understand the difference between our Python and Go logic is the worker file itself:
// pull down pyodide from the public CDN
importScripts('https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js');
addEventListener('message', async (e) => {
// wait for the interpreter to be fully loaded
await languagePluginLoader;
self.runPythonWithStdout = () => {
try {
// execute the code passed to the worker
pyodide.runPython(e.data);
} catch (err){
postMessage({
error: err
});
return;
}
// capture the code's standard output
// and send it back to the main thread
let stdout = pyodide.runPython("sys.stdout.getvalue()")
if (stdout) {
stdout = stdout.split('\n')
for (line of stdout){
postMessage({
message: line
});
}
}
}
// redirect stdout to io.StringIO so that we can get it later
pyodide.runPython(`
import io, code, sys
from js import runPythonWithStdout
sys.stdout = io.StringIO()
sys.stderr = io.StringIO()
## This runs self.runPythonWithStdout defined in the JS
runPythonWithStdout()
`)
postMessage({
done: true
});
}, false);
As you can see, the only particularly challenging part for our use case was adding the glue to properly capture the code’s standard output.
Thanks For Reading!
Follow us on Twitter @q_vault if you have any questions or comments
Take some coding courses on our new platform
Subscribe to our Newsletter for more programming articles
Top comments (0)