Перейти до вмісту

Створення поліглотного застосунку

Поєднайте пакет 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.

Приклади

Чи була ця сторінка корисною?