PyMiniRacer 将 Python 类添加到 JS 范围

问题描述 投票:0回答:2

使用 PyMiniRacer,我将能够在 Javascript 中使用 Python 对象;使用 PyV8 库:

import PyV8

class Scope(PyV8.JsClass):
    def __init__(self):
        self.obj1 = Object1()
        self.obj2 = Object2()

pythonScope = Scope()
context     = PyV8.JsContext(pythonScope)
context.enter()
context.eval(...)

使用此代码,JavaScript 可以访问

Scope
的属性:
obj1
obj2

对于 PyMiniRacer,查看 code

MiniRacer
类似乎不接受构造函数中的任何参数,因此我不知道如何将 Python Scope 类添加到 JS 范围中。是否有一种特定的方法来定义类,以便能够将一组 python 类添加到 JS 范围,或者我是否需要使用我在查看源代码时错过的方法将它们注入到 JS 范围中?

Ruby RubyRacer中,(我知道RubyRacer和PyMiniRacer是不同作者的单独项目,尽管PyMiniRacer受到RubyRacer的启发)Ruby范围内的对象可以通过调用

context["funcName"] = funcName]
来嵌入,但是在Python中......

>>> from py_mini_racer import py_mini_racer
>>> context = py_mini_racer.MiniRacer()
>>> def helloWorld(num):
        return num * 2

>>> context["helloWorld"] = helloWorld
Traceback (most recent call last):
    File "<pyshell#5>", line 1, in <module>
        context["helloWorld"] = helloWorld
TypeError: 'MiniRacer' object does not support item assignment
>>> 

...它会引发错误。我也尝试过

context.__dict__["helloWorld"] = "helloWorld"
,运行
context.eval(helloWorld(5))
返回
ReferenceError: helloWorld is not defined
错误。它所做的只是允许我调用
context.helloWorld(5)
,这对从 JS 执行没有帮助。

如何将Python对象插入到JS作用域中,以便在JS代码中可以调用和访问Python对象的方法和属性?

javascript python scope
2个回答
1
投票

不幸的是,PyMiniRacer 不支持将 Python 对象或函数附加到 JavaScript 上下文,因此无法从 JavaScript 代码调用 Python 代码。


0
投票

我最近恢复了 PyMiniRacer,在 PyPI 中将其重命名为

mini-racer
。截至
v0.12.0
,此问题的答案已更改。

TL;DR:您可能可以做您想做的事情,但不完全按照这个问题提出的方式。

问题是:

如何将Python对象插入到JS作用域中,以便在JS代码中可以调用和访问Python对象的方法和属性?

您仍然无法使用 PyMiniRacer 完全 做到这一点,尽管您可以获得同等的功能。 PyV8(不幸的是,它已经十多年没有维护了)允许您在 Python 中子类化 JavaScript

Object
,而 PyMiniRacer 的绑定则级别稍低一些。如果你真的想“将 Python 对象插入 JS 作用域”,你可以这样做,但这很尴尬,并且可能有更好的方法来实现相同的目标。

我将展示 PyMiniRacer 现在具有的相关功能,那些近似“将 Python 对象插入 JS 范围”的尴尬组合(但在实践中您可能不会完全这样做;您可能会使用第 #1 和 #2 节中的内容,但不是第 #3 节中的内容)。

我将分三部分来处理这个问题:

1.从 Python 构建 JavaScript 对象

您现在可以使用 PyMiniRacer 从 Python 构建 JavaScript 对象:

$ pip install --upgrade mini-racer
...
$ python
>>> from py_mini_racer import MiniRacer
>>> ctx = py_mini_racer.MiniRacer()
>>> scope = ctx.eval('new Object()')
>>> scope['obj1'] = ctx.eval('new Object()')
>>> scope['obj2'] = ctx.eval('new Object()')
>>> ctx.eval('JSON.stringify')(scope)
'{"obj1":{},"obj2":{}}'

2.从 JavaScript 调用 Python 函数

您现在可以使用 PyMiniRacer 将 Python 函数公开给 JavaScript:

这是此功能的文档

关键点:您只能公开

async
Python 方法,这些方法在 JavaScript 端看起来像
async
Promise
。这是因为
async
方法是避免死锁的唯一可靠方法:我们无法阻止 V8 等待 Python 处理回调;我们所能做的就是返回一个
Promise
并稍后完成它。

$ python
>>> from py_mini_racer import MiniRacer
>>> ctx = MiniRacer()
>>> async def hello_world(num):
...   return num * 2
...
>>> async def use_hello_world():
...   async with ctx.wrap_py_function(hello_world) as wrapped:
...     obj = ctx.eval('new Object()')
...     obj['hello'] = wrapped
...     return await ctx.eval('(obj, x) => obj.hello(x)')(obj, 21)
...
>>> import asyncio
>>> asyncio.run(use_hello_world())
42

3.把它放在一起

现在,我们只能公开

async
方法的限制使得这变得很棘手。我们可以使用 JavaScript
Proxy
功能来拦截
getter
setter
调用,但是
Proxy
拦截器天然不支持
async
。这里有一些对此的讨论:How will one do async JavaScript getters and setters?.

设置代码:

(我们可能会将此部分作为辅助库添加到 PyMiniRacer 本身,但这可能太“奇怪”,建议任何人使用它,正如您将在使用示例中看到的那样。)

import asyncio
from contextlib import asynccontextmanager
from py_mini_racer import MiniRacer

ctx = MiniRacer()

@asynccontextmanager
async def expose_object_to_js(obj):
  async def getter(prop):
    return getattr(obj, prop)
  async def setter(prop, value):
    return setattr(obj, prop, value)
  async with (
      ctx.wrap_py_function(getter) as getter_wrapped,
      ctx.wrap_py_function(setter) as setter_wrapped,
    ):
      proxy = ctx.eval("""
(getter, setter) => new Proxy({}, {
  get: function (target, prop, receiver) {
    return getter(prop);
  },
  set: function(obj, prop, value) {
    // Note: we discard the promise here, so we're returning before the setter
    // actually completes.
    setter(prop, value);
  },
})
""")(getter_wrapped, setter_wrapped)
      yield proxy

使用示例:

class Foo:
  def __init__(self):
    self.obj1 = 42
    self.obj2 = 'a_string'

async def demo():
  foo = Foo()
  async with expose_object_to_js(foo) as foo_js:
    obj2_val = await ctx.eval("""
async (foo) => {
  // Set a value on a Python object:
  foo.obj1 = 43;
  // Read a value. Note that we can only read types listed in
  // py_mini_racer.PythonJSConvertedTypes:
  return await foo.obj2;
}
""")(foo_js)
  return foo.obj1, obj2_val

print(asyncio.run(demo()))
# prints: (43, 'a_string')
© www.soinside.com 2019 - 2024. All rights reserved.