Skip to content

Python specific features

Momtchil Momtchev edited this page Dec 17, 2022 · 6 revisions

Operator overloading

Unlike Python, JavaScript does not support operator overloading. This means that some shortcuts in Python are to be expressed using their method call counterparts. For example a[1] is equivalent to a.__getitem__(1) and a > b is equivalent to a.__gt__(b). For example, to add numpy arrays you can do:

const sum = a.get('__add__').call(b); // direct
const sum = a.__add__(b);             // profixied

Refer to the Python manual for the naming of the Python internal methods used for operator overloading.

Alternatively, Python provides a builtin module called operator which exposes the full operator overloading semantics through a function call interface:

const operator = pymport('operator');
const sum = operator.get('add').call(a, b);  // direct
const sum = operator.add(a, b);              // proxified

This has the added benefit of calling the right operator + when the type is not known in advance.

Using objects as subscript indices

Knowing how operator overloading works, even the most perverted pandas syntax can be expressed:

// df = pd.DataFrame(np.arange(15).reshape(5, 3), columns=list(['ABC']) })
const df = pd.DataFrame(np.arange(15).reshape(5, 3), {
  columns: PyObject.list(['A', 'B', 'C']),
});
assert.deepEqual(df.columns.tolist().toJS(), ['A', 'B', 'C']);

// df[2:3]
// In Python this is equivalent to df.__getitem__(2:3)
// In pymport item is a shortcut for __getitem__
// Note that if the underlying object also defines an item() function, it will take precedence
// (for example numpy.ndarray.item will be preferred to PyObject.item)
const df2 = df.item(PyObject.slice({start: 2, stop: 3}));
assert.deepEqual(df2.values.tolist().toJS(), [[6, 7, 8]]);

// df[df['C'] <= 3]
// In Python this is equivalent to df.__getitem__(df.__getitem__('C').__le__(3))
const df3 = df.item(df.item('C').__le__(3));
assert.deepEqual(df3.values.tolist().toJS(), [[0, 1, 2]]);

Lvalues

As JavaScript lacks lvalues, assignments of the form

a[i] = x

are not possible. In fact, under the hood, these are also a special case of operator overloading:

a.__setitem__(i, x);

This also works if x is an object.

Slices

Slices can be expressed and used too:

//memoryview(b'123')[::2]
PyObject.memoryview(Buffer.from('123')).item(PyObject.slice({step: 2}))

[New in 1.2] Python type coercion

Python type coercion of the int(object) type is supported by explicitly calling the desired constructor with the target object:

const i = PyObject.int(obj);        // obj can be any object that implements conversion to int
const f = PyObject.float(obj);      // obj can be any object that implements conversion to float
const l = PyObject.list(iterable);  // iterable can be any iterable
const t = PyObject.tuple(list);     // list must be a list

[New in 1.3] with

Version 1.3 introduces PyObject.with allowing to make use of the Python with semantics:

r = []
with np.nditer(a) as it:
  for a in it:
    r.append(int(a))

becomes (raw access):

const r = [];
np.get('nditer').call(a).with((it: PyObject) => {
  for (const a of it)
    r.push(+a);
});

or (with proxify):

const r = [];
np.nditer(a).with((it: PyObject) => {
  for (const a of it)
    r.push(+a);
});