Zum Inhalt springen

Eine polyglotte App erstellen

Ein npm-Paket und ein PyPI-Paket in einem Elide-Programm kombinieren: Werte, Funktionen und Fehler mit --interop zwischen JavaScript und Python übergeben.

Auf dieser Seite

Diese Seite baut ein Programm, das in einem Prozess ein npm-Paket aus JavaScript und ein PyPI-Paket aus Python nutzt.

VoraussetzungUmsetzung
Pythonelide setup python; elide info führt python unter Languages auf
Aufrufe zwischen Sprachen--interop bei jeder Ausführung; im Manifest gibt es dafür keine Einstellung
npm-Paketedependencies.npm in elide.pkl, danach elide install --ecosystems=npm
PyPI-PaketeEine virtuelle Umgebung mit Python 3.13, ausgewählt über VIRTUAL_ENV

Das Projekt einrichten

// 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

Ein einfaches elide install installiert nur Maven. PyPI-Pakete installiert Elide nicht: elide install --ecosystems=pypi endet mit Exit-Code 2 und pypi installs are not available in this build. Behalten Sie den pypi-Block als Nachweis und installieren Sie die Pakete mit dem pip von Python 3.13 in eine virtuelle Umgebung:

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

Elide lädt Pakete aus $VIRTUAL_ENV/lib/python3.13/site-packages. Reine Python-Pakete funktionieren; manche C-Erweiterungsmodule lassen sich nicht laden (siehe Python).

Das Programm schreiben

# 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

Der Default-Export einer Python-Datei ist ihr Modul; importieren Sie ihn aus einem .mjs-Einstiegspunkt. Zwei häufige Fehler:

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

Werte übergeben

# 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
WertVon JavaScript nach PythonVon Python nach JavaScript
Zahlen, Strings, BooleansNative WerteNative Werte
null, undefined / NoneNone, zurück unverändertnull
Objekt / dictFelder als Attribute lesen (order.item); order["item"] löst TypeError ausSchlüssel als d.key, d["key"] oder d.get("key") lesen; Object.keys, in und JSON.stringify sehen die Methoden des Dicts, nicht seine Schlüssel. Mit Object.fromEntries(d.items()) umwandeln.
Array / listIterierbar, indizierbar, len() funktioniertArray.isArray ist true; map, Spread und Array.from funktionieren
FunktionAufrufbarAufrufbar

JavaScript aus Python aufrufen

Das Modul elide wertet JavaScript aus und gibt dessen Wert zurück:

# 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

Fehler behandeln

Eine Python-Ausnahme erreicht JavaScript als Error, dessen name die Ausnahmeklasse ist; ein JavaScript-Fehler erreicht Python als RuntimeError. Ein nicht abgefangener Fehler gibt einen Stack über beide Sprachen aus:

// 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)

Das Programm endet mit Exit-Code 1.

Asynchronen Code aufrufen

VonFunktioniertFunktioniert nicht
JavaScript ruft Python aufEinen synchronen Wrapper aufrufen, der asyncio.run(...) ausführtawait auf eine Python-Coroutine liefert die Coroutine zurück, ohne sie auszuführen
Python hält ein JavaScript-Promisepromise.then(callback)await promise löst TypeError: object polyglot.ForeignObject can't be used in 'await' expression aus
# 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 blockiert die Event-Loop von JavaScript, bis die Coroutine fertig ist. Python-Threads stehen unter --interop nicht zur Verfügung.

Java und Kotlin

elide run Hello.kt führt Kotlin eigenständig aus; JavaScript und Python können es nicht aufrufen. Ein Import scheitert:

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

Siehe auch Sprachen und Interop, Python und npm-Installer.

Beispiele

War diese Seite hilfreich?