2021-03-14 10:15:53 +00:00
(type-translations)=
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
# Type translations
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
In order to communicate between Python and JavaScript, we "translate" objects
2021-03-14 10:15:53 +00:00
between the two languages. Depending on the type of the object we either
translate the object by implicitly converting it or by proxying it. By
"converting" an object we mean producing a new object in the target language
which is the equivalent of the object from the source language, for example
2021-09-29 08:01:53 +00:00
converting a Python string to the equivalent a JavaScript string. By "proxying"
2021-03-14 10:15:53 +00:00
an object we mean producing a special object in the target language that
2021-09-29 08:01:53 +00:00
forwards requests to the source language. When we proxy a JavaScript object into
2021-09-16 15:30:23 +00:00
Python, the result is a {any}`JsProxy` object. When we proxy a Python object
2021-09-29 08:01:53 +00:00
into JavaScript, the result is a {any}`PyProxy` object. A proxied object can be
2021-09-16 15:30:23 +00:00
explicitly converted using the explicit conversion methods {any}`JsProxy.to_py`
and {any}`PyProxy.toJs`.
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
Python to JavaScript translations occur:
2021-03-14 10:15:53 +00:00
- when returning the final expression from a {any}`pyodide.runPython` call,
2021-09-29 08:01:53 +00:00
- when [importing Python objects into JavaScript ](type-translations_using-py-obj-from-js )
- when passing arguments to a JavaScript function called from Python,
- when returning the results of a Python function called from JavaScript,
2021-03-26 00:44:03 +00:00
- when accessing an attribute of a {any}`PyProxy`
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
JavaScript to Python translations occur:
2018-06-21 15:19:34 +00:00
2021-07-27 04:31:59 +00:00
- when [importing from the `js` module ](type-translations_using-js-obj-from-py )
2021-09-29 08:01:53 +00:00
- when passing arguments to a Python function called from JavaScript
- when returning the result of a JavaScript function called from Python
2021-03-26 00:44:03 +00:00
- when accessing an attribute of a {any}`JsProxy`
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
```{admonition} Memory Leaks and Python to JavaScript translations
2021-03-14 10:15:53 +00:00
:class: warning
2021-09-29 08:01:53 +00:00
Any time a Python to JavaScript translation occurs, it may create a
2021-09-16 15:30:23 +00:00
{any}`PyProxy`. To avoid memory leaks, you must store the {any}`PyProxy` and
{any}`destroy < PyProxy.destroy > ` it when you are done with it. See
{ref}`avoiding-leaks` for more info.
2021-07-26 23:00:27 +00:00
```
2021-03-14 10:15:53 +00:00
2021-03-24 11:05:00 +00:00
## Round trip conversions
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
Translating an object from Python to JavaScript and then back to Python is
2021-09-16 15:30:23 +00:00
guaranteed to give an object that is equal to the original object. Furthermore,
2021-09-29 08:01:53 +00:00
if the object is proxied into JavaScript, then translation back unwraps the
2021-09-16 15:30:23 +00:00
proxy, and the result of the round trip conversion `is` the original object (in
the sense that they live at the same memory address). There are a few
exceptions:
1. `nan` is converted to `nan` after a round trip but `nan != nan`
2. proxies created using {any}`pyodide.create_proxy` will be unwrapped.
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
Translating an object from JavaScript to Python and then back to JavaScript
2021-04-08 23:30:27 +00:00
gives an object that is `===` to the original object. Furthermore, if the object
is proxied into Python, then translation back unwraps the proxy, and the result
of the round trip conversion is the original object (in the sense that they live
at the same memory address). There are a few exceptions:
2021-07-26 23:00:27 +00:00
2021-04-08 23:30:27 +00:00
1. `NaN` is converted to `NaN` after a round trip but `NaN !== NaN` ,
2. `null` is converted to `undefined` after a round trip, and
3. a `BigInt` will be converted to a `Number` after a round trip unless its
absolute value is greater than `Number.MAX_SAFE_INTEGER` (i.e., 2^53).
2021-03-14 10:15:53 +00:00
## Implicit conversions
2021-04-06 22:11:00 +00:00
We implicitly convert immutable types but not mutable types. This ensures that
2021-09-29 08:01:53 +00:00
mutable Python objects can be modified from JavaScript and vice-versa. Python
2021-04-06 22:11:00 +00:00
has immutable types such as `tuple` and `bytes` that have no equivalent in
2021-09-29 08:01:53 +00:00
JavaScript. In order to ensure that round trip translations yield an object of
2021-03-14 10:15:53 +00:00
the same type as the original object, we proxy `tuple` and `bytes` objects.
2021-07-27 04:31:59 +00:00
(type-translations_py2js-table)=
2021-09-29 08:01:53 +00:00
### Python to JavaScript
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
The following immutable types are implicitly converted from JavaScript to
2021-03-14 10:15:53 +00:00
Python:
2018-06-21 15:19:34 +00:00
2021-09-29 08:01:53 +00:00
| Python | JavaScript |
2021-07-26 23:00:27 +00:00
| ------- | ---------------------- |
| `int` | `Number` or `BigInt` \* |
| `float` | `Number` |
| `str` | `String` |
| `bool` | `Boolean` |
| `None` | `undefined` |
2021-04-08 23:30:27 +00:00
2021-09-16 15:30:23 +00:00
\* An `int` is converted to a `Number` if the `int` is between -2^{53} and
2^{53} inclusive, otherwise it is converted to a `BigInt` . (If the browser does
not support `BigInt` then a `Number` will be used instead. In this case,
2021-09-29 08:01:53 +00:00
conversion of large integers from Python to JavaScript is lossy.)
2021-07-27 04:31:59 +00:00
(type-translations_js2py-table)=
2018-06-29 20:34:18 +00:00
2021-09-29 08:01:53 +00:00
### JavaScript to Python
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
The following immutable types are implicitly converted from Python to
2021-09-29 08:01:53 +00:00
JavaScript:
2018-06-29 20:34:18 +00:00
2021-09-29 08:01:53 +00:00
| JavaScript | Python |
2021-07-26 23:00:27 +00:00
| ----------- | --------------------------------- |
| `Number` | `int` or `float` as appropriate\* |
| `BigInt` | `int` |
| `String` | `str` |
| `Boolean` | `bool` |
| `undefined` | `None` |
| `null` | `None` |
2021-04-08 23:30:27 +00:00
2021-07-27 04:31:59 +00:00
\* A number is converted to an `int` if it is between -2^{53} and 2^{53}
inclusive and its fractional part is zero. Otherwise it is converted to a
float.
2018-06-21 15:19:34 +00:00
2021-03-14 10:15:53 +00:00
## Proxying
2018-06-21 15:19:34 +00:00
Any of the types not listed above are shared between languages using proxies
2021-03-14 10:15:53 +00:00
that allow methods and some operations to be called on the object from the other
2018-06-21 15:19:34 +00:00
language.
2021-09-16 15:30:23 +00:00
(type-translations-jsproxy)=
2021-07-27 04:31:59 +00:00
2021-09-29 08:01:53 +00:00
### Proxying from JavaScript into Python
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
When most JavaScript objects are translated into Python a {any}`JsProxy` is
2021-09-16 15:30:23 +00:00
returned. The following operations are currently supported on a {any}`JsProxy`:
2021-07-27 04:31:59 +00:00
2021-09-29 08:01:53 +00:00
| Python | JavaScript |
2021-07-27 04:31:59 +00:00
| ---------------------------------- | --------------------------------- |
| `str(proxy)` | `x.toString()` |
| `proxy.foo` | `x.foo` |
| `proxy.foo = bar` | `x.foo = bar` |
| `del proxy.foo` | `delete x.foo` |
| `hasattr(proxy, "foo")` | `"foo" in x` |
| `proxy(...)` | `x(...)` |
| `proxy.foo(...)` | `x.foo(...)` |
| {any}`proxy.new(...)< JsProxy.new > ` | `new X(...)` |
| `len(proxy)` | `x.length` or `x.size` |
| `foo in proxy` | `x.has(foo)` or `x.includes(foo)` |
| `proxy[foo]` | `x.get(foo)` |
| `proxy[foo] = bar` | `x.set(foo, bar)` |
| `del proxy[foo]` | `x.delete(foo)` |
| `proxy1 == proxy2` | `x === y` |
| `proxy.typeof` | `typeof x` |
| `iter(proxy)` | `x[Symbol.iterator]()` |
| `next(proxy)` | `x.next()` |
| `await proxy` | `await x` |
2021-09-29 08:01:53 +00:00
Note that each of these operations is only supported if the proxied JavaScript
2021-07-27 04:31:59 +00:00
object supports the corresponding operation. See {any}`the JsProxy API docs < JsProxy > ` for the rest of the methods supported on {any}`JsProxy`. Some other
code snippets:
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
```py
for v in proxy:
# do something
```
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
is equivalent to:
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
```js
2021-07-26 23:00:27 +00:00
for (let v of x) {
// do something
2021-03-14 10:15:53 +00:00
}
```
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
The `dir` method has been overloaded to return all keys on the prototype chain
of `x` , so `dir(x)` roughly translates to:
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
```js
2021-07-26 23:00:27 +00:00
function dir(x) {
let result = [];
do {
result.push(...Object.getOwnPropertyNames(x));
} while ((x = Object.getPrototypeOf(x)));
return result;
2021-03-14 10:15:53 +00:00
}
```
2020-10-30 20:09:25 +00:00
2021-09-29 08:01:53 +00:00
As a special case, JavaScript `Array` , `HTMLCollection` , and `NodeList` are
2021-03-14 10:15:53 +00:00
container types, but instead of using `array.get(7)` to get the 7th element,
2021-09-29 08:01:53 +00:00
JavaScript uses `array[7]` . For these cases, we translate:
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
| Python | JavaScript |
2021-07-26 23:00:27 +00:00
| ------------------ | ------------------- |
2021-07-27 04:31:59 +00:00
| `proxy[idx]` | `array[idx]` |
| `proxy[idx] = val` | `array[idx] = val` |
2021-07-26 23:00:27 +00:00
| `idx in proxy` | `idx in array` |
2021-07-27 04:31:59 +00:00
| `del proxy[idx]` | `array.splice(idx)` |
2021-03-14 10:15:53 +00:00
2021-03-25 16:26:07 +00:00
(type-translations-pyproxy)=
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
### Proxying from Python into JavaScript
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
When most Python objects are translated to JavaScript a {any}`PyProxy` is
2021-09-16 15:30:23 +00:00
produced.
2021-03-26 00:44:03 +00:00
2021-09-29 08:01:53 +00:00
Fewer operations can be overloaded in JavaScript than in Python so some
2021-09-16 15:30:23 +00:00
operations are more cumbersome on a {any}`PyProxy` than on a {any}`JsProxy`. The
following operations are supported:
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
| JavaScript | Python |
2021-07-26 23:00:27 +00:00
| ----------------------------------- | ------------------- |
| `foo in proxy` | `hasattr(x, 'foo')` |
| `proxy.foo` | `x.foo` |
| `proxy.foo = bar` | `x.foo = bar` |
| `delete proxy.foo` | `del x.foo` |
| `Object.getOwnPropertyNames(proxy)` | `dir(x)` |
| `proxy(...)` | `x(...)` |
| `proxy.foo(...)` | `x.foo(...)` |
| `proxy.length` | `len(x)` |
| `proxy.has(foo)` | `foo in x` |
| `proxy.get(foo)` | `x[foo]` |
| `proxy.set(foo, bar)` | `x[foo] = bar` |
| `proxy.delete(foo)` | `del x[foo]` |
| `proxy.type` | `type(x)` |
| `proxy[Symbol.iterator]()` | `iter(x)` |
| `proxy.next()` | `next(x)` |
| `await proxy` | `await x` |
````{admonition} Memory Leaks and PyProxy
2021-03-14 10:15:53 +00:00
:class: warning
2018-06-24 16:29:46 +00:00
2021-07-27 04:31:59 +00:00
Make sure to destroy PyProxies when you are done with them to avoid memory leaks.
See {ref}`avoiding-leaks`.
2018-06-24 16:29:46 +00:00
```javascript
2021-03-24 11:05:00 +00:00
let foo = pyodide.globals.get('foo');
2021-03-14 10:15:53 +00:00
foo();
2018-06-24 16:29:46 +00:00
foo.destroy();
2021-03-14 10:15:53 +00:00
foo(); // throws Error: Object has already been destroyed
```
2021-07-26 23:00:27 +00:00
````
2021-03-14 10:15:53 +00:00
## Explicit Conversion of Proxies
2021-03-25 16:26:07 +00:00
(type-translations-pyproxy-to-js)=
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
### Python to JavaScript
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
Explicit conversion of a {any}`PyProxy` into a native JavaScript object is done
2021-07-27 04:31:59 +00:00
with the {any}`PyProxy.toJs` method. You can also perform such a conversion in
Python using {any}`to_js < pyodide.to_js > ` which behaves in much the same way. By
default, the `toJs` method does a recursive "deep" conversion, to do a shallow
conversion use `proxy.toJs({depth : 1})` . In addition to [the normal type
conversion](type-translations_py2js-table), `toJs` method performs the following
explicit conversions:
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
| Python | JavaScript |
2021-07-26 23:00:27 +00:00
| --------------- | ------------ |
| `list` , `tuple` | `Array` |
| `dict` | `Map` |
| `set` | `Set` |
| a buffer\* | `TypedArray` |
2021-04-09 04:51:20 +00:00
2021-07-27 04:31:59 +00:00
\* Examples of buffers include bytes objects and numpy arrays.
If you need to convert `dict` instead to `Object` , you can pass
`Object.fromEntries` as the `dict_converter` argument:
`proxy.toJs({dict_converter : Object.fromEntries})` .
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
In JavaScript, `Map` and `Set` keys are compared using object identity unless
2021-03-14 10:15:53 +00:00
the key is an immutable type (meaning a string, a number, a bigint, a boolean,
`undefined` , or `null` ). On the other hand, in Python, `dict` and `set` keys are
compared using deep equality. If a key is encountered in a `dict` or `set` that
2021-09-29 08:01:53 +00:00
would have different semantics in JavaScript than in Python, then a
2021-03-14 10:15:53 +00:00
`ConversionError` will be thrown.
2021-04-09 04:51:20 +00:00
See {ref}`buffer_tojs` for the behavior of `toJs` on buffers.
2021-07-27 04:31:59 +00:00
````{admonition} Memory Leaks and toJs
2021-03-14 10:15:53 +00:00
:class: warning
2021-07-27 04:31:59 +00:00
The {any}`toJs < PyProxy.toJs > ` method can create many proxies at arbitrary
depth. It is your responsibility to manually `destroy` these proxies if you wish
to avoid memory leaks. The `pyproxies` argument to `toJs` is designed to help
with this:
2021-03-14 10:15:53 +00:00
```js
2021-07-27 04:31:59 +00:00
let pyproxies = [];
proxy.toJs({pyproxies});
// Do stuff
// pyproxies contains the list of proxies created by `toJs` . We can destroy them
// when we are done with them
for(let px of pyproxies){
px.destory();
2021-03-14 10:15:53 +00:00
}
2021-07-27 04:31:59 +00:00
proxy.destroy();
2021-03-14 10:15:53 +00:00
```
2021-07-27 04:31:59 +00:00
As an alternative, if you wish to assert that the object should be fully
converted and no proxies should be created, you can use
`proxy.toJs({create_proxies : false})` . If a proxy would be created, an error is
raised instead.
````
2018-06-21 15:19:34 +00:00
2021-04-13 16:30:08 +00:00
(type-translations-jsproxy-to-py)=
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
### JavaScript to Python
2021-07-26 23:00:27 +00:00
2021-09-16 15:30:23 +00:00
Explicit conversion of a {any}`JsProxy` into a native Python object is done with
the {any}`JsProxy.to_py` method. By default, the `to_py` method does a recursive
"deep" conversion, to do a shallow conversion use `proxy.to_py(depth=1)` The
`to_py` method performs the following explicit conversions:
2021-03-14 10:15:53 +00:00
2021-09-29 08:01:53 +00:00
| JavaScript | Python |
2021-07-27 04:31:59 +00:00
| ---------- | ------ |
| `Array` | `list` |
| `Object` \* | `dict` |
| `Map` | `dict` |
| `Set` | `set` |
2021-03-14 10:15:53 +00:00
2021-09-16 15:30:23 +00:00
\* `to_py` will only convert an object into a dictionary if its constructor is
`Object` , otherwise the object will be left alone. Example:
2021-07-26 23:00:27 +00:00
2021-03-14 10:15:53 +00:00
```pyodide
class Test {};
window.x = { "a" : 7, "b" : 2};
window.y = { "a" : 7, "b" : 2};
Object.setPrototypeOf(y, Test.prototype);
pyodide.runPython(`
from js import x, y
# x is converted to a dictionary
2021-03-24 11:05:00 +00:00
assert x.to_py() == { "a" : 7, "b" : 2}
2021-03-14 10:15:53 +00:00
# y is not a "Plain Old JavaScript Object", it's an instance of type Test so it's not converted
assert y.to_py() == y
`);
2018-06-21 15:19:34 +00:00
```
2021-09-29 08:01:53 +00:00
In JavaScript, `Map` and `Set` keys are compared using object identity unless
2021-03-14 10:15:53 +00:00
the key is an immutable type (meaning a string, a number, a bigint, a boolean,
`undefined` , or `null` ). On the other hand, in Python, `dict` and `set` keys are
compared using deep equality. If a key is encountered in a `Map` or `Set` that
2021-09-29 08:01:53 +00:00
would have different semantics in Python than in JavaScript, then a
`ConversionError` will be thrown. Also, in JavaScript, `true !== 1` and `false !== 0` , but in Python, `True == 1` and `False == 0` . This has the result that a
JavaScript map can use `true` and `1` as distinct keys but a Python `dict`
cannot. If the JavaScript map contains both `true` and `1` a `ConversionError`
2021-03-14 10:15:53 +00:00
will be thrown.
2018-06-21 15:19:34 +00:00
2021-09-16 15:30:23 +00:00
## Functions
2021-09-29 08:01:53 +00:00
### Calling Python objects from JavaScript
2021-09-16 15:30:23 +00:00
If a Python object is callable, the proxy will be callable too. The arguments
2021-09-29 08:01:53 +00:00
will be translated from JavaScript to Python as appropriate, and the return
value will be translated from JavaScript back to Python. If the return value is
2021-09-16 15:30:23 +00:00
a `PyProxy` , you must explicitly destroy it or else it will be leaked.
An example:
```pyodide
let test = pyodide.runPython(`
def test(x):
return [n*n for n in x]
test
`);
let result_py = test([1,2,3,4]);
// result_py is a PyProxy of a list.
let result_js = result_py.toJs();
// result_js is the array [1, 4, 9, 16]
result_py.destroy();
```
2021-09-29 08:01:53 +00:00
If a function is indended to be used from JavaScript, you can use {any}`to_js < pyodide.to_js > ` on the return value. This prevents the return value from
leaking without requiring the JavaScript code to explicitly destroy it. This is
2021-09-16 15:30:23 +00:00
particularly important for callbacks.
```pyodide
let test = pyodide.runPython(`
from pyodide import to_js
def test(x):
return to_js([n*n for n in x])
test
`);
let result = test([1,2,3,4]);
// result is the array [1, 4, 9, 16], nothing needs to be destroyed.
```
2021-09-29 08:01:53 +00:00
If you need to use a key word argument, use {any}`callKwargs < PyProxy.callKwargs > `. The last argument should be a JavaScript object with the
2021-09-16 15:30:23 +00:00
key value arguments.
```pyodide
let test = pyodide.runPython(`
from pyodide import to_js
def test(x, *, offset):
return to_js([n*n + offset for n in x])
to_js(test)
`);
let result = test.callKwargs([1,2,3,4], { offset : 7});
// result is the array [8, 12, 16, 23]
```
(call-js-from-py)=
2021-09-29 08:01:53 +00:00
### Calling JavaScript functions from Python
2021-09-16 15:30:23 +00:00
2021-09-29 08:01:53 +00:00
What happens when calling a JavaScript function from Python is a bit more
complicated than calling a Python function from JavaScript. If there are any
keyword arguments, they are combined into a JavaScript object and used as the
2021-09-16 15:30:23 +00:00
final argument. Thus, if you call:
```py
f(a=2, b=3)
```
2021-09-29 08:01:53 +00:00
then the JavaScript function receives one argument which is a JavaScript object
2021-09-16 15:30:23 +00:00
`{a : 2, b : 3}` .
2021-09-29 08:01:53 +00:00
When a JavaScript function is called and it returns anything but a promise, if
2021-09-16 15:30:23 +00:00
the result is a `PyProxy` it is destroyed. Also, any arguments that are
PyProxies that were created in the process of argument conversion are also
destroyed. If the `PyProxy` was created in Python using
{any}`pyodide.create_proxy` it is not destroyed.
2021-09-29 08:01:53 +00:00
When a JavaScript function returns a `Promise` (for example, if the function is
2021-09-16 15:30:23 +00:00
an `async` function), it is assumed that the `Promise` is going to do some work
that uses the arguments of the function, so it is not safe to destroy them until
the `Promise` resolves. In this case, the proxied function returns a Python
`Future` instead of the original `Promise` . When the `Promise` resolves, the
result is converted to Python and the converted value is used to resolve the
`Future` . Then if the result is a `PyProxy` it is destroyed. Any PyProxies
created in converting the arguments are also destroyed at this point.
As a result of this, if a `PyProxy` is persisted to be used later, then it must
2021-09-29 08:01:53 +00:00
either be copied using {any}`PyProxy.copy` in JavaScript or it must be created
2021-09-16 15:30:23 +00:00
with {any}`pyodide.create_proxy` or `pyodide.create_once_callable` . If it's only
going to be called once use `pyodide.create_once_callable` :
```py
from pyodide import create_once_callable
from js import setTimeout
def my_callback():
print("hi")
setTimeout(create_once_callable(my_callback), 1000)
```
If it's going to be called many times use `create_proxy` :
```py
from pyodide import create_proxy
from js import document
def my_callback():
print("hi")
proxy = document.create_proxy(my_callback)
document.body.addEventListener("click", proxy)
# ...
# make sure to hold on to proxy
document.body.removeEventListener("click", proxy)
proxy.destroy()
```
2021-03-14 10:15:53 +00:00
## Buffers
2019-05-01 18:56:30 +00:00
2021-09-29 08:01:53 +00:00
### Using JavaScript Typed Arrays from Python
2019-05-01 18:56:30 +00:00
2021-09-29 08:01:53 +00:00
JavaScript ArrayBuffers and ArrayBuffer views (`Int8Array` and friends) are
2021-04-16 06:46:39 +00:00
proxied into Python. Python can't directly access arrays if they are outside of
the wasm heap so it's impossible to directly use these proxied buffers as Python
buffers. You can convert such a proxy to a Python `memoryview` using the `to_py`
2021-09-16 15:30:23 +00:00
api. This makes it easy to correctly convert the array to a Numpy array using
`numpy.asarray` :
2019-05-01 18:56:30 +00:00
2021-04-16 06:46:39 +00:00
```pyodide
self.jsarray = new Float32Array([1,2,3, 4, 5, 6]);
pyodide.runPython(`
from js import jsarray
array = jsarray.to_py()
import numpy as np
numpy_array = np.asarray(array).reshape((2,3))
print(numpy_array)
`);
2021-03-14 10:15:53 +00:00
```
2021-07-26 23:00:27 +00:00
2021-04-16 06:46:39 +00:00
After manipulating `numpy_array` you can assign the value back to
`jsarray` using {any}`JsProxy.assign`:
2021-07-26 23:00:27 +00:00
2021-04-16 06:46:39 +00:00
```pyodide
pyodide.runPython(`
numpy_array[1,1] = 77
jsarray.assign(a)
`);
console.log(jsarray); // [1, 2, 3, 4, 77, 6]
2019-05-01 18:56:30 +00:00
```
2021-04-16 06:46:39 +00:00
The {any}`JsProxy.assign` and {any}`JsProxy.assign_to` methods can be used to
2021-09-29 08:01:53 +00:00
assign a JavaScript buffer from / to a Python buffer which is appropriately
2021-04-16 06:46:39 +00:00
sized and contiguous. The assignment methods will only work if the data types
match, the total length of the buffers match, and the Python buffer is
contiguous.
These APIs are currently experimental, hopefully we will improve them in the
future.
2021-04-09 04:51:20 +00:00
(buffer_tojs)=
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
### Using Python Buffer objects from JavaScript
2021-07-26 23:00:27 +00:00
2021-04-09 04:51:20 +00:00
Python objects supporting the [Python Buffer
protocol](https://docs.python.org/3/c-api/buffer.html) are proxied into
2021-09-29 08:01:53 +00:00
JavaScript. The data inside the buffer can be accessed via the
2021-09-16 15:30:23 +00:00
{any}`PyProxy.toJs` method or the {any}`PyProxy.getBuffer` method. The `toJs`
2021-09-29 08:01:53 +00:00
API copies the buffer into JavaScript, whereas the `getBuffer` method allows low
2021-09-16 15:30:23 +00:00
level access to the WASM memory backing the buffer. The `getBuffer` API is more
powerful but requires care to use correctly. For simple use cases the `toJs` API
should be prefered.
2021-04-09 04:51:20 +00:00
If the buffer is zero or one-dimensional, then `toJs` will in most cases convert
it to a single `TypedArray` . However, in the case that the format of the buffer
2021-09-16 15:30:23 +00:00
is `'s'` , we will convert the buffer to a string and if the format is `'?'` we
will convert it to an Array of booleans.
2021-04-09 04:51:20 +00:00
2021-09-29 08:01:53 +00:00
If the dimension is greater than one, we will convert it to a nested JavaScript
2021-09-16 15:30:23 +00:00
array, with the innermost dimension handled in the same way we would handle a 1d
array.
2021-04-09 04:51:20 +00:00
An example of a case where you would not want to use the `toJs` method is when
the buffer is bitmapped image data. If for instance you have a 3d buffer shaped
1920 x 1080 x 4, then `toJs` will be extremely slow. In this case you could use
{any}`PyProxy.getBuffer`. On the other hand, if you have a 3d buffer shaped 1920
x 4 x 1080, the performance of `toJs` will most likely be satisfactory.
Typically the innermost dimension won't matter for performance.
The {any}`PyProxy.getBuffer` method can be used to retrieve a reference to a
2021-09-29 08:01:53 +00:00
JavaScript typed array that points to the data backing the Python object,
2021-03-26 22:59:06 +00:00
combined with other metadata about the buffer format. The metadata is suitable
2021-09-29 08:01:53 +00:00
for use with a JavaScript ndarray library if one is present. For instance, if
you load the JavaScript [ndarray ](https://github.com/scijs/ndarray ) package, you
2021-04-09 04:51:20 +00:00
can do:
2021-07-26 23:00:27 +00:00
2021-03-26 22:59:06 +00:00
```js
let proxy = pyodide.globals.get("some_numpy_ndarray");
let buffer = proxy.getBuffer();
proxy.destroy();
try {
2021-07-26 23:00:27 +00:00
if (buffer.readonly) {
// We can't stop you from changing a readonly buffer, but it can cause undefined behavior.
throw new Error("Uh-oh, we were planning to change the buffer");
}
let array = new ndarray(
buffer.data,
buffer.shape,
buffer.strides,
buffer.offset
);
// manipulate array here
// changes will be reflected in the Python ndarray!
2021-03-26 22:59:06 +00:00
} finally {
2021-07-26 23:00:27 +00:00
buffer.release(); // Release the memory when we're done
2021-03-26 22:59:06 +00:00
}
```
2021-03-14 10:15:53 +00:00
2021-04-06 08:14:07 +00:00
(type-translations-errors)=
2021-07-26 23:00:27 +00:00
2021-09-16 15:30:23 +00:00
## Errors
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
All entrypoints and exit points from Python code are wrapped in JavaScript `try`
blocks. At the boundary between Python and JavaScript, errors are caught,
2021-04-06 08:14:07 +00:00
converted between languages, and rethrown.
2021-09-29 08:01:53 +00:00
JavaScript errors are wrapped in a {any}`JsException < pyodide.JsException > `.
2021-04-06 08:14:07 +00:00
Python exceptions are converted to a {any}`PythonError < pyodide.PythonError > `.
2021-09-29 08:01:53 +00:00
At present if an exception crosses between Python and JavaScript several times,
2021-04-06 08:14:07 +00:00
the resulting error message won't be as useful as one might hope.
In order to reduce memory leaks, the {any}`PythonError < pyodide.PythonError > `
has a formatted traceback, but no reference to the original Python exception.
The original exception has references to the stack frame and leaking it will
leak all the local variables from that stack frame. The actual Python exception
will be stored in
[`sys.last_value` ](https://docs.python.org/3/library/sys.html#sys.last_value ) so
if you need access to it (for instance to produce a traceback with certain
functions filtered out), use that.
2021-07-27 04:31:59 +00:00
```{admonition} Be careful Proxying Stack Frames
2021-04-06 08:14:07 +00:00
:class: warning
If you make a {any}`PyProxy` of ``sys.last_value``, you should be especially
careful to {any}`destroy() < PyProxy.destroy > ` it when you are done with it or
you may leak a large amount of memory if you don't.
2021-07-26 23:00:27 +00:00
```
2021-04-06 08:14:07 +00:00
The easiest way is to only handle the exception in Python:
2021-07-26 23:00:27 +00:00
2021-04-06 08:14:07 +00:00
```pyodide
pyodide.runPython(`
def reformat_exception():
from traceback import format_exception
# Format a modified exception here
# this just prints it normally but you could for instance filter some frames
return "".join(
traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback)
)
`);
let reformat_exception = pyodide.globals.get("reformat_exception");
try {
pyodide.runPython(some_code);
} catch(e){
// replace error message
e.message = reformat_exception();
throw e;
}
```
2021-04-18 14:10:45 +00:00
2021-09-16 15:30:23 +00:00
## Importing Objects
2021-09-11 23:34:45 +00:00
2021-09-16 15:30:23 +00:00
It is possible to access objects in one languge from the global scope in the
other language. It is also possible to create custom namespaces and access
objects on the custom namespaces.
2021-09-11 23:34:45 +00:00
2021-09-16 15:30:23 +00:00
(type-translations_using-py-obj-from-js)=
2021-09-11 23:34:45 +00:00
2021-09-29 08:01:53 +00:00
### Importing Python objects into JavaScript
2021-09-11 23:34:45 +00:00
2021-09-29 08:01:53 +00:00
A Python object in the `__main__` global scope can imported into JavaScript
2021-09-16 15:30:23 +00:00
using the {any}`pyodide.globals.get < PyProxy.get > ` method. Given the name of the
2021-09-29 08:01:53 +00:00
Python object to import, it returns the object translated to JavaScript.
2021-09-11 23:34:45 +00:00
2021-09-16 15:30:23 +00:00
```js
let sys = pyodide.globals.get("sys");
2021-09-11 23:34:45 +00:00
```
2021-09-16 15:30:23 +00:00
As always, if the result is a `PyProxy` and you care about not leaking the
Python object, you must destroy it when you are done. It's also possible to set
values in the Python global scope with {any}`pyodide.globals.set < PyProxy.set > `
or remove them with {any}`pyodide.globals.delete < PyProxy.delete > `:
2021-09-11 23:34:45 +00:00
```pyodide
2021-09-16 15:30:23 +00:00
pyodide.globals.set("x", 2);
pyodide.runPython("print(x)"); // Prints 2
2021-09-11 23:34:45 +00:00
```
2021-09-16 15:30:23 +00:00
If you execute code with a custom globals dictionary, you can use a similar
approach:
2021-07-26 23:00:27 +00:00
2021-04-18 14:10:45 +00:00
```pyodide
2021-09-16 15:30:23 +00:00
let my_py_namespace = pyodide.globals.get("dict")();
pyodide.runPython("x=2", my_py_namespace);
let x = my_py_namespace.get("x");
2021-04-18 14:10:45 +00:00
```
2021-07-26 23:00:27 +00:00
2021-09-16 15:30:23 +00:00
(type-translations_using-js-obj-from-py)=
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
### Importing JavaScript objects into Python
2021-04-18 14:10:45 +00:00
2021-09-29 08:01:53 +00:00
JavaScript objects in the
2021-09-16 15:30:23 +00:00
[`globalThis` ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis )
global scope can be imported into Python using the `js` module.
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
When importing a name from the `js` module, the `js` module looks up JavaScript
attributes of the `globalThis` scope and translates the JavaScript objects into
2021-09-16 15:30:23 +00:00
Python.
2021-07-26 23:00:27 +00:00
2021-04-18 14:10:45 +00:00
```py
2021-09-16 15:30:23 +00:00
import js
js.document.title = 'New window title'
from js.document.location import reload as reload_page
reload_page()
2021-04-18 14:10:45 +00:00
```
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
You can also assign to JavaScript global variables in this way:
2021-07-26 23:00:27 +00:00
2021-09-16 15:30:23 +00:00
```pyodide
pyodide.runPython("js.x = 2");
console.log(window.x); // 2
2021-04-18 14:10:45 +00:00
```
2021-07-26 23:00:27 +00:00
2021-09-29 08:01:53 +00:00
You can create your own custom JavaScript modules using
2021-09-16 15:30:23 +00:00
{any}`pyodide.registerJsModule` and they will behave like the `js` module except
with a custom scope:
2021-07-26 23:00:27 +00:00
2021-09-16 15:30:23 +00:00
```pyodide
let my_js_namespace = { x : 3 };
pyodide.registerJsModule("my_js_namespace", my_js_namespace);
pyodide.runPython(`
from my_js_namespace import x
print(x) # 3
my_js_namespace.y = 7
`);
console.log(my_js_namespace.y); // 7
2021-04-18 14:10:45 +00:00
```