问题
I'm trying to pass Python data (lists, dicts, strings..., arbitrarily nested) to 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
The code above prints None, presumably because the data object is not transformed to a JSArray and thus data.length
evaluates to undefined
. Is there a reliable way to do the necessary conversion in PyV8 other than using JSON?
回答1:
Apparently PyV8 doesn't correctly convert python lists to Javascript arrays, which leads my_list.length
to return undefined
, which is getting converted to 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
The answer is to use PyV8.JSArray(my_list)
. I've written the following helper functions for my project that deal with various little problems and make it easy to convert back and forth between python and js objects. These are targeted at a specific version of PyV8 however (which is the only version I can recommend, see discussion in the linked issues), so your results may vary if you use them as-is. Example usage:
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
And here are the functions.
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
来源:https://stackoverflow.com/questions/28642999/converting-python-objects-to-javascript-for-pyv8