Pyscript/Pyodide and JS Object Passing (Original)
Published August 21, 2022
A question I've been seeing quite a bit over in the Unofficial PyScript Community Discord is: How do you pass objects back and forth between JavaScript and PyScript/Pyodide? So I've created recipies below for passing objects back and forth between JavaScript and Python; the specifics are somewhat different depending on whether we're working in PyScript or directly in Pyodide, so both options are illustrated below.
Currently, you can:
- ✅ Pass objects from JavaScript to Python running in PyScript
- ✅ Pass objects from JavaScript Python running in Pyodide
- ✅ Pass objects from Python running in Pyodide to JavaScript
For our purposes, an 'object' is anything that can be bound to a variable (a number, string, object, function, etc). Also, recall that the import js
or from js import ...
in Pyodide gets objects from the JavaScript globalThis scope, so keep the rules of JavaScript variable scoping in mind.
JavaScript to Python (PyScript)
We can use the simple from js import ...
to import JavaScript objects directly into PyScript.
Javascript to Python (PyScript)
|
|
|
|
|
|
JavaScript to Python (Pyodide)
We can also use from js import ...
to import JavaScript objects directly into Python in Pyodide. The syntax is identical to the PyScript example above - the <py-script> calls the runPython
function for us (among other things).
Javascript to Python (Pyodide)
|
|
Python (Pyodide) to JavaScript
One we've initialized the Pyodide runtime, the JS object pyodide.globals
is a mapping that represents the global Python namespace. We can use the get()
method to retrieve an object from this mapping and make use of it in JavaScript.
Python (Pyodide) to JavaScript
|
|
Python (PyScript) to JavaScript
Since PyScript doesn't export its instance of Pyodide and only one instance of Pyodide can be running in a browser window at a time, there isn't currently a way for Javascript to access Objects defined inside PyScript tags "directly".
However, I've found a workaround using JavaScript's eval() function, which executes a string as code much like Python's eval(). First, we create a JS function createObject
which takes an object and a string, then uses eval() to bind that string as a variable to that object. By calling this function from PyScript (where we have access to the Pyodide global namespace), we can bind JavaScript variables to Python objects without having direct access to that global namespace.
|
|
This takes a Python Object and creates a variable pointing to it in the JavaScript global scope. So what if we made a JavaScript variable point at... the Python global namespace?
exportGlobals.py
from js import createObject
from pyodide.ffi import create_proxy
createObject(create_proxy(globals()), "pyodideGlobals")
This, amazingly, just works. All Python global variables are now accessible at in JavaScript with the syntax pyodideGlobals.get('myVariableName')
Let's see an example running live. The three buttons below print the values of the variables x
, y
, and z
respectively, as looked up in the Python global namespace. Use the REPL to set the values of those variables, and see how JavaScript goes from seeing them as "undefined" to their value in PyScript.
I've pre-populated an example line in the REPL for you. Click the '' or press shift-enter
to run the current REPL line.
#button-output
buttons.js
|
|
|
|
|
|
A Deeper Dive
We don't have to export the entire Python global namespace as an object if we don't want to. The example below shows exporting a single list and a lambda function as JavaScript variables, using the same createObject
function above.
Note that the names of the JavaScript variable and the Python variable don't have to be similar/identical/different - I've named them similarly ('names'
and 'names_js'
, 'mutliplier'
and 'multiplier_js'
) for readability.
|
|
|
|
The code above binds the JavaScript variable names_js
to a PyProxy of the Python list names
, and the JavaScript variables multiplier_js
to a PyProxy for the Python lambda function multiplier
.
Of course, this means we have to use the createObject function to "export" the objects from Python before we can use them in JavaScript. But this may be preferred for your use case.
With those objects created, we can refer to/call them like any other JS objects. To see this, let's add two buttons: one that references our function and list from within JavaScript ("use-python-objects"), and one that adds some names to our list so we can see it change ("add-name").
|
|
|
|
|
|
|
|
|
|
|
|
Python (PyScript) Individual Objects to JavaScript Demo
The code in the preceding section is running live on this page. Click "Add Name to List" to append a new name (provided by the Faker library) to the list names
; click "Use Python Objects" to reference that list (and the multiplier
function) and display the results in the green box. Open your browser's development console to see additional output.
#output:
Viewing globals()
Since we have a reference to the PyScript global namespace, we can also just view its contents from JavaScript. And again so we can see it really changing, let's add a button that creates new Python objects with random names using exec():
displayGlobals.js
|
|
|
|
|
|
makeNewObjects.py
|
|
Click the Print Globals button to see the Python global objects visible from JavaScript; click the Make Python Variable to make a new Python variable with a 5-letter name (then click Print Globals again to see it). Since this shares a global namespace with the rest of the PyScript code on this page, you may also see variables like 'x
', 'y
', and 'z
' from the example above.
#globals: