Saltar al contenido

Crear una aplicación políglota

Combine un paquete de npm y un paquete de PyPI en un mismo programa de Elide: pase valores, funciones y errores entre JavaScript y Python con --interop.

En esta página

Esta página crea un programa que usa un paquete de npm desde JavaScript y un paquete de PyPI desde Python, en un solo proceso.

RequisitoCómo
Pythonelide setup python; elide info muestra python en Languages
Llamadas entre lenguajes--interop en cada ejecución; no hay ningún ajuste en el manifiesto
Paquetes de npmdependencies.npm en elide.pkl y después elide install --ecosystems=npm
Paquetes de PyPIUn entorno virtual de Python 3.13, seleccionado con VIRTUAL_ENV

Configurar el proyecto

// elide.pkl
amends "elide:project.pkl"

name = "poly"

dependencies {
  npm { packages { "pretty-ms@9.3.1" } }
  pypi { packages { "humanize==4.12.3" } }
}
elide install --ecosystems=npm

Un elide install sin opciones solo instala Maven. Elide no instala paquetes de PyPI: elide install --ecosystems=pypi termina con el código 2 y el mensaje pypi installs are not available in this build. Conserve el bloque pypi como registro e instale los paquetes con el pip de Python 3.13 en un entorno virtual:

python3.13 -m venv .venv
.venv/bin/pip install humanize==4.12.3
export VIRTUAL_ENV=$PWD/.venv

Elide carga los paquetes desde $VIRTUAL_ENV/lib/python3.13/site-packages. Los paquetes escritos solo en Python funcionan; algunos módulos de extensión en C no se cargan (consulte Python).

Escribir el programa

# report.py
import humanize

def size(n):
    return humanize.naturalsize(n)

def summarize(files):
    total = sum(f.bytes for f in files)
    return {"count": len(files), "total": humanize.naturalsize(total)}
// app.mjs
import prettyMs from "pretty-ms";
import report from "./report.py";

const files = [
  { name: "build.log", bytes: 1_500_000, ageMs: 172_800_000 },
  { name: "test.log", bytes: 32_000, ageMs: 5_400_000 },
];

for (const f of files) {
  console.log(`${f.name}: ${report.size(f.bytes)}, ${prettyMs(f.ageMs)} old`);
}

const summary = report.summarize(files);
console.log(`${summary.count} files, ${summary.total}`);
elide --interop run app.mjs
build.log: 1.5 MB, 2d old
test.log: 32.0 kB, 1h 30m old
2 files, 1.5 MB

La exportación por defecto de un archivo de Python es su módulo; impórtelo desde un punto de entrada .mjs. Dos fallos habituales:

FaltaError
--interopError: Cannot import 'report.py': JavaScript entries run without Python. Re-run with --interop to use it.
VIRTUAL_ENVModuleNotFoundError: No module named 'humanize'

Pasar valores

# shapes.py
def label(order):
    return f"{order.item} x{order.qty}"

def doubled(prices):
    return [p * 2 for p in prices]

def config():
    return {"region": "eu", "retries": 3}

def apply(fn, value):
    return fn(value)

def fail():
    raise ValueError("bad input")
// values.mjs
import shapes from "./shapes.py";

console.log(shapes.label({ item: "bolt", qty: 4 }));
console.log(shapes.doubled([1, 2.5, 3]).map((n) => n + 1));
const config = shapes.config();
console.log(config.region, config.get("retries"));
console.log(JSON.stringify(Object.fromEntries(config.items())));
console.log(shapes.apply((s) => s.toUpperCase(), "shout"));
console.log(shapes.apply((v) => v, null), shapes.apply((v) => v, undefined));
elide --interop run values.mjs
bolt x4
(3)[3, 6, 7]
eu 3
{"region":"eu","retries":3}
SHOUT
null undefined
ValorDe JavaScript a PythonDe Python a JavaScript
Números, cadenas, booleanosValores nativosValores nativos
null, undefined / NoneNone, y de vuelta sin cambiosnull
Objeto / dictLos campos se leen como atributos (order.item); order["item"] lanza TypeErrorLas claves se leen como d.key, d["key"] o d.get("key"); Object.keys, in y JSON.stringify ven los métodos del dict, no sus claves. Conviértalo con Object.fromEntries(d.items()).
Array / listIterable e indexable; len() funcionaArray.isArray es true; map, la sintaxis spread y Array.from funcionan
FunciónInvocableInvocable

Llamar a JavaScript desde Python

El módulo elide evalúa JavaScript y devuelve su valor:

# calls.py
from elide import js

slug = js("(s) => s.trim().toLowerCase().replaceAll(' ', '-')")
print(slug("  Hello Polyglot World "))

point = js("({ x: 1, y: 2, tags: ['a', 'b'] })")
print(point.x + point.y, point.tags[0], len(point.tags))
print(sorted(js("[3, 1, 2]")))

try:
    js("JSON.parse('{oops')")
except RuntimeError as e:
    print("caught:", e)

js("Promise.resolve(42)").then(lambda v: print("resolved:", v))
elide --interop python calls.py
hello-polyglot-world
3 a 2
[1, 2, 3]
caught: JSException: SyntaxError: Unexpected token o in JSON at position 1
resolved: 42

Manejar errores

Una excepción de Python llega a JavaScript como un Error cuyo name es la clase de la excepción; un error de JavaScript llega a Python como RuntimeError. Un error no capturado imprime una sola pila que abarca ambos lenguajes:

// errors.mjs
import shapes from "./shapes.py";

try {
  shapes.fail();
} catch (e) {
  console.log(e.name, "|", e.message);
}

shapes.apply((x) => {
  throw new RangeError(`bad value ${x}`);
}, 7);
elide --interop run errors.mjs
ValueError | ValueError: bad input
RangeError: bad value 7
    at :=> (errors.mjs:10:9)
    at apply (shapes.py:11:12)
    at :module:eval (errors.mjs:9:1)

El programa termina con 1.

Llamar a código asíncrono

DesdeFuncionaNo funciona
JavaScript que llama a PythonLlamar a un envoltorio síncrono que ejecuta asyncio.run(...)await sobre una corrutina de Python devuelve la corrutina sin ejecutarla
Python con una promesa de JavaScriptpromise.then(callback)await promise lanza TypeError: object polyglot.ForeignObject can't be used in 'await' expression
# rates.py
import asyncio

async def fetch_rate(code):
    await asyncio.sleep(0.01)
    return {"EUR": 0.92, "GBP": 0.79}[code]

def rate(code):
    return asyncio.run(fetch_rate(code))

def when_ready(promise, done):
    promise.then(lambda value: done(f"python saw {value}"))
// rates.mjs
import rates from "./rates.py";

console.log("rate:", rates.rate("EUR"));

const total = new Promise((resolve) => setTimeout(() => resolve(7), 10));
rates.when_ready(total, (msg) => console.log(msg));
await total;
elide --interop run rates.mjs
rate: 0.92
python saw 7

asyncio.run bloquea el bucle de eventos de JavaScript hasta que termina la corrutina. Los hilos de Python no están disponibles con --interop.

Java y Kotlin

elide run Hello.kt ejecuta Kotlin por separado; JavaScript y Python no pueden llamarlo. Importarlo falla:

import hello from "./Hello.kt";
TypeError: Unsupported file extension: 'file:/path/to/Hello.kt'

Consulte también Lenguajes e interoperabilidad, Python e Instalador npm.

Ejemplos

¿Te resultó útil esta página?