ExecJS

项目介绍

该库不再维护。错误不会被修复(即使它们是微不足道的或必不可少的)。

我们建议使用其他库或创建一个分支。


从 Python 运行 JavaScript 代码。

PyExecJS 是从 Ruby 移植 ExecJS。PyExecJS会自动 选择可用于评估 JavaScript 程序的最佳运行时。

一个简短的例子:

>>> import execjs
>>> execjs.eval("'red yellow blue'.split(' ')")
['red', 'yellow', 'blue']
>>> ctx = execjs.compile("""
...     function add(x, y) {
...         return x + y;
...     }
... """)
>>> ctx.call("add", 1, 2)
3

支持的运行时

一流的支持(提供并测试运行时类)

二等支持(运行时类提供但未测试)

安装

$ pip install PyExecJS

或者

$ easy_install PyExecJS

细节

如果指定了EXECJS_RUNTIME环境变量,PyExecJS 会选择 JavaScript 运行时作为默认值:

>>> execjs.get().name # this value is depends on your environment.
>>> os.environ["EXECJS_RUNTIME"] = "Node"
>>> execjs.get().name
'Node.js (V8)'

您可以通过execjs.get()选择 JavaScript 运行时:

>>> default = execjs.get() # the automatically picked runtime
>>> default.eval("1 + 2")
3
>>> import execjs.runtime_names
>>> jscript = execjs.get(execjs.runtime_names.JScript)
>>> jscript.eval("1 + 2")
3
>>> import execjs.runtime_names
>>> node = execjs.get(execjs.runtime_names.Node)
>>> node.eval("1 + 2")
3

PyExecJS 的优点是你不需要照顾 JavaScript 环境。特别是,它无需安装额外的库即可在 Windows 环境中运行。

PyExecJS 的缺点之一是性能。PyExecJS 通过文本与 JavaScript 运行时通信,速度很慢。另一个缺点是它不完全支持特定于运行时的功能。

对于某些用例,PyV8可能是更好的选择。

异常

python 运行 execjs 出现异常:pyexecjs UnicodeEncodeError: 'gbk' codec can't encode character '\xa0' in position 68162:

解决办法1:编辑 "C:\Python36\Lib\subprocess.py" 修改 encoding=Noneencoding="utf-8"

解决办法2:安装bug修复版本:PyExecJS2,项目地址:pip install PyExecJS2==1.6.1

Js2Py

github

纯 Python JavaScript 翻译器/解释器

一切都是在 100% 纯 Python 中完成的,因此安装和使用起来非常容易。支持 Python 2 & 3。完全支持 ECMAScript 5.1,ECMA 6 支持仍处于试验阶段。


简单示例:

    >>> import js2py
    >>> js2py.eval_js('console.log( "Hello World!" )')
    'Hello World!'
    >>> add = js2py.eval_js('function add(a, b) {return a + b}')
    >>> add(1, 2) + 3
    6
    >>> add.constructor
    function Function() { [python code] }
    >>> js2py.require('underscore')
    'function _(obj) { [python code] }'

您还可以导入大量节点模块,就好像它们是用 Python 编写的一样!例如,这里我们导入一个纯 JS 库crypto-js

    >>> CryptoJS = js2py.require('crypto-js')
    >>> data = [{'id': 1}, {'id': 2}]
    >>> JSON = js2py.eval_js('JSON')
    >>> ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123')
    >>> bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), 'secret key 123')
    >>> decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8)).to_list()
    >>> decryptedData
    [{u'id': 1}, {u'id': 2}]

现在还支持 JavaScript 6(仍处于试验阶段):

    >>> js2py.eval_js6('let a = () => 11; a()')
    11

JavaScript 6 支持是通过使用 Js2Py 翻译名为Babel 的javascript 库来实现的。Babel 将 JS 6 翻译成 JS 5,然后 Js2Py 将 JS 5 翻译成 Python。唯一的缺点是翻译的 babel.js 大约有 4 MB,导入这么长的 Python 文件大约需要 15 秒!


翻译 JavaScript 文件:

    # this will translate example.js to example.py
    >>> js2py.translate_file('example.js', 'example.py')
    # example.py can be now imported and used!
    >>> from example import example
    >>> example.someFunction()
    ...

ECMA 5.1 的每个功能都已实现(“with”语句除外):

>>> js2py.eval_js("Object.prototype.toString.call(Function('s', 'return s+arguments[1]')(new Date(), 7).__proto__)")
[object String]

不幸的是,尽管 Js2Py 通常可用于翻译巨大的 Js 文件(超过 50k 行),但在极少数情况下,您可能会遇到一些意想不到的问题(例如 javascript 调用带有 300 个参数的函数 - python 只允许 255 个)。这些问题很难用当前的翻译方法解决。我将尝试在不久的将来实现一个解释器,它有望修复所有边缘情况。

安装

pip install js2py

更高级的用法示例

js_file = r'xxx.js'
with open(js_file, 'r', encoding='UTF-8') as f:
    js_code = f.read()

#实例化解析js对象
context = js2py.EvalJs()

#js转换为python代码
context.execute(js_code)

#调⽤js中的o函数,t为o的参数
result = context.o(t)
print(result)

可以使用 EvalJs 从 JS 范围访问所有变量。此外,如果将 Python 对象添加到作用域,则可以使用 JavaScript 代码中的 Python 对象。在这个例子中,我们将使用 Python 内置的求和函数对 JS 数组中的元素求和。它将位于 python_sum 之下。

# Adding Python built-in sum to the JS context:
>>> context = js2py.EvalJs({'python_sum': sum})
>>> js_code = '''
var a = 10
function f(x) {return x*x}
'''
>>> context.execute(js_code)
# Get value of variable a:
>>> context.a
10
# context.f behaves just like js function so you can supply more than 1 argument. '9'*'9' in javascript is 81.
>>> context.f('9', 0)
81
# context.f has all attributes of normal JavaScript object
>>> context.f.toString()
u'function f(x) { [python code] }'
>>> context.f.bind
function bind(thisArg) { [python code] }
# You can also add variables to the context:
>>> context.foo = [1,2,3]  # context.foo is now Js Array object and behaves just like javascript array!
>>> context.foo.push(4)
4
>>> context.foo.to_list() # convert to python list
[1, 2, 3, 4]
# You can use Python objects that you put inside the context!
>>> context.eval('python_sum(new Array(1, 2, 3))')
6

您还可以像这样在 JavaScript 中启用 require 支持:

>>> context = js2py.EvalJs(enable_require=True)
>>> context.eval("require('esprima').parse('var a = 1')")

Python 中的 JavaScript“虚拟机”

作为一个有趣的实验项目,我还实现了一个基于 VM 的 JavaScript(是的——在这个 repo 中有 2 个独立的 JS 实现)。它比基于翻译的版本功能完整且速度更快。下面你可以看到一个带有漂亮调试视图的演示(字节码+执行序列):

>>> from js2py.internals import seval
>>> seval.eval_js_vm("try {throw 3+3} catch (e) {console.log(e)}", debug=True)
[LOAD_UNDEFINED(),
 JUMP(4,),
 LABEL(1,),
 LOAD_UNDEFINED(),
 POP(),
 LOAD_NUMBER(3.0,),
 LOAD_NUMBER(3.0,),
 BINARY_OP('+',),
 THROW(),
 NOP(),
 LABEL(2,),
 LOAD_UNDEFINED(),
 POP(),
 LOAD('console',),
 LOAD('e',),
 LOAD_N_TUPLE(1,),
 CALL_METHOD_DOT('log',),
 NOP(),
 LABEL(3,),
 LOAD_UNDEFINED(),
 NOP(),
 LABEL(4,),
 TRY_CATCH_FINALLY(1, 2, 'e', 3, False, 4)]

0 LOAD_UNDEFINED()
1 JUMP(4,)
18 TRY_CATCH_FINALLY(1, 2, 'e', 3, False, 4)
  ctx entry (from:2, to:9)
  2 LOAD_UNDEFINED()
  3 POP()
  4 LOAD_NUMBER(3.0,)
  5 LOAD_NUMBER(3.0,)
  6 BINARY_OP('+',)
  7 THROW()
  ctx exit (js errors)
  ctx entry (from:9, to:16)
  9 LOAD_UNDEFINED()
  10 POP()
  11 LOAD('console',)
  12 LOAD('e',)
  13 LOAD_N_TUPLE(1,)
  14 CALL_METHOD_DOT('log',)
6
  15 NOP()
  ctx exit (normal)

这只是一种好奇心,我不建议在实践中使用 VM(需要更多改进)。


限制

主要有3个限制:

它们在实践中通常不是大问题。在实践中,更多的问题是不幸的是有时确实会发生的次要边缘情况。如果发现错误,请报告错误。

Js2Py 能够成功翻译和运行巨大的 JS 库,如 Babel(100k+ loc)、esprima、crypto-js 等。您可以通过导入任何受支持的 npm 包来自己尝试js2py.require('your_package')


其他例子

在 Js2Py 中,所有 JavaScript 对象都是 PyJs 对象的子类。例如 JS Number 由 PyJsNumber 类表示。js2py.eval_js 和 js2py.EvalJs 自动尝试将 PyJs 类型转换为内置 python 类型。因此,例如,如果您执行:

>>> js2py.eval_js('var a = "hello"; a')

eval_js 将返回 unicode 类型 (u"hello")。但是,对于复杂类型,这种转换是不可能的,因此会返回 JsObjectWrapper。看转换表JsType -> PyType:

Boolean -> bool
String -> unicode (str in Python 3)
Number -> float (or int/long if whole number)
undefined -> None
null -> None
OTHER -> JsObjectWrapper

JsObjectWrapper 支持:getitem、getattr、setitem、setattr、repr 和 call。此外,如果您想将其转换为内置 python 类型,它还具有 to_list 和 to_dict 方法。

>>> js = js2py.eval_js('d = {a:1, b:2}')
>>> js
{a: 1, b: 2}
>>> type(js)
<class 'js2py.base.JsObjectWrapper'>
>>> js.a
1
>>> js['a']
1
>>> js.b = 20
>>> js
{a: 1, b: 20}
>>> js['c'] = 30
>>> js.to_dict()
{u'a': 1, 'c': 30, u'b': 20}

另外,当然你可以使用 Js2Py 来解析(tree 和 esprima.js 中一样)和翻译 JavaScript

解析:

>>> js2py.parse_js('var $ = 5')
{
    "body": [
        {
            "declarations": [
                {
                    "id": {
                        "name": "$",
                        "type": "Identifier"
                    },
                    "init": {
                        "raw": "5",
                        "type": "Literal",
                        "value": 5
                    },
                    "type": "VariableDeclarator"
                }
            ],
            "kind": "var",
            "type": "VariableDeclaration"
        }
    ],
    "type": "Program"
}

翻译:

>>> print(js2py.translate_js('var $ = 5'))
from js2py.pyjs import *
# setting scope
var = Scope( JS_BUILTINS )
set_global_object(var)

# Code follows:
var.registers(['$'])
var.put('$', Js(5.0))

pyimport声明

最后,Js2Py 还支持使用“pyimport”语句从 JavaScript 导入任何 Python 代码:

>>> x = """pyimport urllib;
           var result = urllib.urlopen('https://www.google.com/').read();
           console.log(result.length)
        """
>>> js2py.eval_js(x)
18211

通过subprocess.Popen系统调用

import subprocess

p = subprocess.Popen("mode", stdout=subprocess.PIPE, universal_newlines=True).stdout.read()