使用 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对象的方法和属性?
不幸的是,PyMiniRacer 不支持将 Python 对象或函数附加到 JavaScript 上下文,因此无法从 JavaScript 代码调用 Python 代码。
我最近恢复了 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 节中的内容)。
我将分三部分来处理这个问题:
您现在可以使用 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":{}}'
您现在可以使用 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
现在,我们只能公开
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')