Перейти к содержимому

Создание полиглотного приложения

Объедините пакет npm и пакет PyPI в одной программе Elide: передавайте значения, функции и ошибки между JavaScript и Python с --interop.

На этой странице

На этой странице строится программа, которая в одном процессе использует пакет npm из JavaScript и пакет PyPI из Python.

Что нужноКак
Pythonelide setup python; elide info показывает python в разделе Languages
Вызовы между языками--interop при каждом запуске; настройки в манифесте для этого нет
Пакеты npmdependencies.npm в elide.pkl, затем elide install --ecosystems=npm
Пакеты PyPIВиртуальное окружение Python 3.13, выбранное через VIRTUAL_ENV

Настройка проекта

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

Простой elide install устанавливает только Maven. Пакеты PyPI Elide не устанавливает: elide install --ecosystems=pypi завершается с кодом 2 и сообщением pypi installs are not available in this build. Оставьте блок pypi как запись о зависимостях и установите пакеты через pip из Python 3.13 в виртуальное окружение:

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

Elide загружает пакеты из $VIRTUAL_ENV/lib/python3.13/site-packages. Пакеты на чистом Python работают; некоторые модули расширений на C не загружаются (см. Python).

Написание программы

# 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

Экспорт по умолчанию у файла Python — это его модуль; импортируйте его из точки входа .mjs. Две частые ошибки:

Чего не хватаетОшибка
--interopError: Cannot import 'report.py': JavaScript entries run without Python. Re-run with --interop to use it.
VIRTUAL_ENVModuleNotFoundError: No module named 'humanize'

Передача значений

# 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
ЗначениеИз JavaScript в PythonИз Python в JavaScript
Числа, строки, булевы значенияНативные значенияНативные значения
null, undefined / NoneNone, обратно без измененийnull
Объект / dictПоля читаются как атрибуты (order.item); order["item"] выбрасывает TypeErrorКлючи читаются как d.key, d["key"] или d.get("key"); Object.keys, in и JSON.stringify видят методы словаря, а не его ключи. Преобразуйте через Object.fromEntries(d.items()).
Массив / listИтерируемый, индексируемый, len() работаетArray.isArray равно true; map, spread и Array.from работают
ФункцияМожно вызватьМожно вызвать

Вызов JavaScript из Python

Модуль elide вычисляет JavaScript и возвращает его значение:

# 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

Обработка ошибок

Исключение Python приходит в JavaScript как Error, у которого name — класс исключения; ошибка JavaScript приходит в Python как RuntimeError. Неперехваченная ошибка выводит один стек через оба языка:

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

Программа завершается с кодом 1.

Вызов асинхронного кода

ОткудаРаботаетНе работает
JavaScript вызывает PythonВызов синхронной обёртки, которая выполняет asyncio.run(...)await для корутины Python возвращает саму корутину, не выполняя её
Python держит промис JavaScriptpromise.then(callback)await promise выбрасывает 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 блокирует цикл событий JavaScript, пока корутина не завершится. Потоки Python под --interop недоступны.

Java и Kotlin

elide run Hello.kt запускает Kotlin отдельно; JavaScript и Python не могут его вызывать. Импорт завершается ошибкой:

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

См. также Языки и взаимодействие, Python и Установщик npm.

Примеры

Была ли эта страница полезной?