انتقل إلى المحتوى

إنشاء تطبيق متعدد اللغات

اجمع حزمة 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 فقط. ولا يثبّت Elide حزم ⁦PyPI:⁩ إذ ينتهي 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.

أمثلة

هل كانت هذه الصفحة مفيدة؟