为 PyV8 将 Python 个对象转换为 JavaScript

Converting Python objects to JavaScript for PyV8

我正在尝试将 Python 数据(列表、字典、字符串...,任意嵌套)传递给 PyV8。

class Global(object):
    def __init__(self, data):
        self.data = data
ctx = PyV8.JSContext(Global([{'a':1}]))
ctx.enter()
res = ctx.eval('data.length')
js_len = PyV8.convert(res)
print js_len

上面的代码打印 None,大概是因为数据对象没有转换为 JSArray,因此 data.length 的计算结果为 undefined。除了使用 JSON?

之外,是否有可靠的方法在 PyV8 中进行必要的转换

显然 PyV8 没有正确地将 python 列表转换为 Javascript 数组,这导致 my_list.length 到 return undefined,它正在转换为None.

ctx = PyV8.JSContext()
ctx.enter()
ctx.locals.a = [{'a':1}]
print ctx.locals.a
#> [{'a': 1}]
print ctx.eval("a.length")
#> None
print ctx.eval("a[0].a")
#> 1
ctx.locals.blub = {'a':1}
print ctx.eval("blub.a")
#> 1
print ctx.eval("Object.keys(blub)")
#> a
ctx.locals.blub = {'a':[1,2,3]}
print ctx.eval("Object.keys(blub)")
#> a
print ctx.eval("blub.a")
#> [1, 2, 3]
ctx.locals.blub2 = [{'a':[1,2,3]}]
print ctx.eval("blub2")
#> [{'a': [1, 2, 3]}]
print ctx.eval("blub2.length")
#> None
print ctx.eval("Array.isArray(blub2)")
#> False
print ctx.eval("typeof(blub2)")
#> object
print ctx.eval("blub2[0].a")
#> [1, 2, 3]
print ctx.eval("typeof(blub.a)")
#> object
print ctx.eval("Array.isArray(blub.a)")
#> False

答案是使用PyV8.JSArray(my_list)。我为我的项目编写了以下帮助函数,它们可以处理各种小问题,并可以轻松地在 python 和 js 对象之间来回转换。这些是针对 at a specific version of PyV8 however (which is the only version I can recommend, see discussion in the linked issues) 的,因此如果您按原样使用它们,您的结果可能会有所不同。用法示例:

ctx.locals.blub3 = get_js_obj({'a':[1,2,3]})
ctx.locals.blub4 = get_js_obj([1,2,3])
ctx.eval("blub3.a.length")
#> 3
ctx.eval("blub4.length")
#> 3

这是函数。

def access_with_js(ctx, route):
    if len(route) == 0:
        raise Exception("route must have at least one element")
    accessor_string = route[0]
    for elem in route[1:]:
        if type(elem) in [str, unicode]:
            accessor_string += "['" + elem + "']"
        elif type(elem) == int:
            accessor_string += "[" + str(elem) + "]"
        else:
            raise Exception("invalid element in route, must be text or number")
    return ctx.eval(accessor_string)

def get_py_obj(ctx, obj, route=[]):
    def dict_is_empty(dict):
        for key in dict:
            return False
        return True

    def access(obj, key):
        if key in obj:
            return obj[key]
        return None

    cloned = None
    if isinstance(obj, list) or isinstance(obj, PyV8.JSArray):
        cloned = []
        temp = str(access_with_js(ctx, route)) #working around a problem with PyV8 r429
        num_elements = len(obj)
        for index in range(num_elements):
            elem = obj[index]
            cloned.append(get_py_obj(ctx, elem, route + [index]))
    elif isinstance(obj, dict) or isinstance(obj, PyV8.JSObject):
        cloned = {}
        for key in obj.keys():
            cloned_val = None
            if type(key) == int:
                #workaround for a problem with PyV8 where it won't let me access
                #objects with integer accessors
                val = None
                try:
                    val = access(obj, str(key))
                except KeyError:
                    pass
                if val == None:
                    val = access(obj, key)
                cloned_val = get_py_obj(ctx, val, route + [key])
            else:
                cloned_val = get_py_obj(ctx, access(obj, key), route + [key])
            cloned[key] = cloned_val
    elif type(obj) == str:
        cloned = obj.decode('utf-8')
    else:
        cloned = obj
    return cloned

def get_js_obj(ctx,obj):
    #workaround for a problem with PyV8 where it will implicitely convert python lists to js objects
    #-> we need to explicitely do the conversion. see also the wrapper classes for JSContext above.
    if isinstance(obj, list):
        js_list = []
        for entry in obj:
            js_list.append(get_js_obj(ctx,entry))
        return PyV8.JSArray(js_list)
    elif isinstance(obj, dict):
        js_obj = ctx.eval("new Object();") # PyV8.JSObject cannot be instantiated from Python
        for key in obj.keys():

            try:
                js_obj[key] = get_js_obj(ctx,obj[key])
            except Exception, e:
                # unicode keys raise a Boost.Python.ArgumentError 
                # which can't be caught directly:
                # https://mail.python.org/pipermail/cplusplus-sig/2010-April/015470.html
                if (not str(e).startswith("Python argument types in")):
                    raise
                import unicodedata
                js_obj[unicodedata.normalize('NFKD', key).encode('ascii','ignore')] = get_js_obj(ctx,obj[key])
        return js_obj
    else:
        return obj