pyodide/docs/type_conversions.md

200 lines
6.5 KiB
Markdown
Raw Normal View History

(type_conversions)=
2018-06-21 15:19:34 +00:00
# Type conversions
Python to Javascript conversions occur:
- when returning the final expression from a
{ref}`pyodide.runPython <js_api_pyodide_runPython>` call
(evaluating a Python cell in Iodide)
- using {ref}`pyodide.pyimport <js_api_pyodide_pyimport>`
2018-06-21 15:19:34 +00:00
- passing arguments to a Javascript function from Python
Javascript to Python conversions occur:
- when using the `from js import ...` syntax
- returning the result of a Javascript function to Python
## Basic types
The following basic types are implicitly converted between Javascript and
Python. The values are copied and any connection to the original object is lost.
2018-06-22 15:49:32 +00:00
| Python | Javascript |
|-----------------|---------------------|
| `int`, `float` | `Number` |
| `str` | `String` |
| `True` | `true` |
| `False` | `false` |
| `None` | `undefined`, `null` |
| `list`, `tuple` | `Array` |
| `dict` | `Object` |
2018-06-21 15:19:34 +00:00
2018-06-29 20:34:18 +00:00
## Typed arrays
Javascript typed arrays (Int8Array and friends) are converted to Python
`memoryviews`. This happens with a single binary memory copy (since Python can't
access arrays on the Javascript heap), and the data type is preserved. This
makes it easy to correctly convert it to a Numpy array using `numpy.asarray`:
```javascript
array = Float32Array([1, 2, 3])
```
```python
from js import array
import numpy as np
numpy_array = np.asarray(array)
```
Python `bytes` and `buffer` objects are converted to Javascript as
`Uint8ClampedArray`s, without any memory copy at all, and is thus very
efficient, but be aware that any changes to the buffer will be reflected in both
places.
Numpy arrays are currently converted to Javascript as nested (regular) Arrays. A
more efficient method will probably emerge as we decide on an ndarray
implementation for Javascript.
2018-06-21 15:19:34 +00:00
## Class instances
Any of the types not listed above are shared between languages using proxies
that allow methods and some operators to be called on the object from the other
language.
2018-06-25 20:54:08 +00:00
### Javascript from Python
2018-06-21 15:19:34 +00:00
2018-06-22 15:49:32 +00:00
When passing a Javascript object to Python, an extension type is used to
2018-06-21 15:19:34 +00:00
delegate Python operations to the Javascript side. The following operations are
currently supported. (More should be possible in the future -- work in ongoing
to make this more complete):
2018-06-25 20:54:08 +00:00
| Python | Javascript |
|----------------|-----------------|
| `repr(x)` | `x.toString()` |
| `x.foo` | `x.foo` |
| `x.foo = bar` | `x.foo = bar` |
| `del x.foo` | `delete x.foo` |
| `x(...)` | `x(...)` |
| `x.foo(...)` | `x.foo(...)` |
| `X.new(...)` | `new X(...)` |
| `len(x)` | `x.length` |
| `x[foo]` | `x[foo]` |
| `x[foo] = bar` | `x[foo] = bar` |
| `del x[foo]` | `delete x[foo]` |
| `x == y` | `x == y` |
| `x.typeof` | `typeof x` |
One important difference between Python objects and Javascript objects is that
if you access a missing member in Python, an exception is raised. In Javascript,
it returns `undefined`. Since we can't make any assumptions about whether the
Javascript member is missing or simply set to `undefined`, Python mirrors the
Javascript behavior. For example:
```javascript
// Javascript
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
point = new Point(42, 43))
```
```python
# python
from js import point
assert point.y == 43
del point.y
assert point.y is None
```
### Python from Javascript
2018-06-21 15:19:34 +00:00
When passing a Python object to Javascript, the Javascript [Proxy
API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
is used to delegate Javascript operations to the Python side. In general, the
Proxy API is more limited than what can be done with a Python extension, so
there are certain operations that are impossible or more cumbersome when using
Python from Javascript than vice versa. The most notable limitation is that
2018-06-22 15:49:32 +00:00
while Python has distinct ways of accessing attributes and items (`x.foo` and
2018-06-21 15:19:34 +00:00
`x[foo]`), Javascript conflates these two concepts. The following operations are
currently supported:
2018-06-22 15:49:32 +00:00
| Javascript | Python |
|----------------|--------------------------|
| `foo in x` | `hasattr(x, 'foo')` |
| `x.foo` | `getattr(x, 'foo')` |
| `x.foo = bar` | `setattr(x, 'foo', bar)` |
| `delete x.foo` | `delattr(x, 'foo')` |
| `x.ownKeys()` | `dir(x)` |
| `x(...)` | `x(...)` |
| `x.foo(...)` | `x.foo(...)` |
2018-06-21 15:19:34 +00:00
An additional limitation is that when passing a Python object to Javascript,
there is no way for Javascript to automatically garbage collect that object.
Therefore, custom Python objects must be manually destroyed when passed to Javascript, or
they will leak. To do this, call `.destroy()` on the object, after which Javascript will no longer have access to the object.
```javascript
var foo = pyodide.pyimport('foo');
foo.call_method();
foo.destroy();
foo.call_method(); // This will raise an exception, since the object has been
// destroyed
```
2018-06-21 15:19:34 +00:00
2018-06-22 15:49:32 +00:00
## Using Python objects from Javascript
2018-06-21 15:19:34 +00:00
A Python object (in global scope) can be brought over to Javascript using the
{ref}`pyodide.pyimport <js_api_pyodide_pyimport>` function. It takes a string
giving the name of the variable, and returns the object, converted to
Javascript.
2018-06-21 15:19:34 +00:00
```javascript
var sys = pyodide.pyimport('sys');
```
(type_conversions_using_js_obj_from_py)=
2018-06-22 15:49:32 +00:00
## Using Javascript objects from Python
2018-06-21 15:19:34 +00:00
Javascript objects can be accessed from Python using the special `js` module.
This module looks up attributes of the global (`window`) namespace on the
Javascript side.
2018-06-21 15:19:34 +00:00
```python
import js
js.document.title = 'New window title'
```
### Performance considerations
Looking up and converting attributes of the `js` module happens dynamically. In
most cases, where the value is small or results in a proxy, this is not an
issue. However, if the value takes a long time to convert from Javascript to
Python, you may want to store it in a Python variable or use the `from js import
...` syntax.
For example, given this large Javascript variable:
```javascript
var x = new Array(1000).fill(0)
```
Use it from Python as follows:
```python
import js
x = js.x # conversion happens once here
for i in range(len(x)):
item = x[i] # we don't pay the conversion price each time here
```
Or alternatively:
```python
from js import x # conversion happens once here
for i in range(len(x)):
item = x[i] # we don't pay the conversion price each time here
2018-06-21 15:19:34 +00:00
```