家好,我是DD,已经是封闭在家的第51天了!
最近一直在更新Java新特性(https://www.didispace.com/java-features/)和IDEA Tips(https://www.didispace.com/idea-tips/)两个原创专栏,其他方向内容的动态关注少了。昨天天晚上刷推的时候,瞄到了这个神奇的东西,觉得挺cool的,拿出来分享下:
相信你看到图,不用我说,你也猜到是啥了吧?html里可以跑python代码了!
看到好多Python公众号已经开始猛吹未来了,但乍看怎么觉得有点像JSP?或者一些模版引擎?是进步还是倒退呢?与其瞎想,不如仔细看看这个东东的能力吧!
根据官方介绍,这个名为PyScript的框架,其核心目标是为开发者提供在标准HTML中嵌入Python代码的能力,使用 Python调用JavaScript函数库,并以此实现利用Python创建Web应用的功能。
看到介绍里提到了调用JavaScript函数库的能力,看来跟JSP或者模版引擎还是有区别的。
PyScript 快速体验
官方给了一个例子,可以帮助我们观的感受这个开发框架的能力,不妨跟着DD看看,它能做啥吧!
第一个案例,hello world
代码很简单,就下面这几行。你只需要创建一个html文件,然后复制进去就可以了。
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
</head>
<body>
<py-script>
print('Hello, World!')
</py-script>
</body>
</html>
|
保存好之后,在浏览器里打开就能看到这样的页面了:
回头再看看这个html里的内容,三个核心内容:
- 引入pyscript的样式文件:<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
- 引入pyscript的脚本文件:<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
- <py-script>标签中写具体的python代码来输出Hello World
如果你懒得自己敲代码的话,本文的两个案例代码我打包放在公众号了,需要的朋友可以关注公众号“程序猿DD”,回复:pyscript 获取。
第二个案例,数据定义 + 数据展示
先创建一个data.py文件,然后加入前面的代码。功能很简单,就是随机生成(x,y)的坐标
import numpy as np
def make_x_and_y(n):
x = np.random.randn(n)
y = np.random.randn(n)
return x, y
|
再创建一个html文件,加入下面的代码
<html>
<head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<py-env>
- numpy
- matplotlib
- paths:
- /data.py
</py-env>
</head>
<body>
<h1>Let's plot random numbers</h1>
<div id="plot"></div>
<py-script output="plot">
import matplotlib.pyplot as plt
from data import make_x_and_y
x, y = make_x_and_y(n=1000)
fig, ax = plt.subplots()
ax.scatter(x, y)
fig
</py-script>
</body>
</html>
|
这里就稍微复杂一些了,除了hello world中的几个要点外,这里还有这几个要关注的地方:
- <py-env>标签:这里声明要引入的包和要引入的文件(上面创建的data.py)
- <py-script output="plot">:这里定义了要在<div id="plot"></div>中输出的内容,可以看到这里的逻辑都是用python写的
这个页面的执行效果是这样的:
是不是很神奇呢?整个过程中都没有大家熟悉的cs、js内容,就完成了这样一个图的页面实现。
小结
最后,谈谈在整个尝试过程中,给我的几个感受:
- 开发体验上高度统一,对于python开发者来说,开发Web应用的门槛可以更低了
- 感觉性能上似乎有所不足,几个复杂的案例执行有点慢,开始以为是部分国外cdn的缘故,后来移到本地后,还是慢。这部分可能还需要进一步优化。
这个开发框架目前还只是alpha版本,未来一定还会有更多特性与优化出来,总体上我觉得这个框架还是非常cool的,尤其对于刚学会Python,或者只会Python,但又想快速开发Web应用的小伙伴来说,可能将会是个不错的选择,那你觉得这个框架如何?未来会不会火?留言区聊聊吧!
avaScript 和 Python 是世界上最流行和最常用的两种语言。JavaScript 是前端和后端 Web 开发不可或缺的一部分。Python 更适合后端和快速应用程序开发。
两者各有所长,可以通过从 Python 代码中运行 JavaScript 代码来强强联合,达到两全其美。
JavaScript
JavaScript 是一种用于 Web 开发的脚本语言。可以使用 JavaScript 向网页添加行为和功能。是一种解释性语言,这意味着代码实时运行,而无需编译器将其转换为机器代码。
JavaScrip 语法特征:
代码块:JavaScript 使用大括号 {} 定义;
变量:使用 var 关键字定义变量。语法: var variable_name = value;
常量:使用 const 关键字定义常量。语法:constant_name = value;
输入/输出:使用 window.prompt() 在 JavaScript 中获取输入,并使用 console.log() 在控制台上显示输出。
Python
Python 是一种高级编程语言,在后端开发、人工智能和数据科学中得到了广泛应用。Python 也是一种释型语言。
Python 语法特征:
代码块:Python 使用缩进来定义;
变量: variable_name = value;
常量:Python 并不真正支持常量,但约定成俗规定大写变量视为常量,例如 CONSTANT_NAME;
输入/输出:使用 input() 获取输入,并使用 print()显示输出。
js2Py 模块
Js2Py 是一个 JavaScript 到 Python 的翻译组件,要使用此模块,请打开终端并执行安装:
pip install js2py
Js2Py 自动将任何有效的 JavaScript 转换为 Python,而无需使用任何依赖项。可以将大部分 JavaScript 代码转换为 Python 语言。
在 Python 中运行 JavaScript 代码的示例
1.我们从经典的“Hello World”开始。
import js2py
js2py.eval_js('console.log("Hello World!")')
将 JavaScript 代码作为参数传递给 eval_js() 以对其进行转换。在 Python 的输出终端上,显示“Hello World!”。
2.两个数字相加
在 Python 中使用 JavaScript 执行两个数字相加的方法。
import js2py
js_add = '''function add(a, b){return a + b;}'''
add = js2py.eval_js(js_add)
print(add(3, 7))
使用 JavaScript 声明一个函数,并将其存储在字符串中。使用函数 eval_js() 以将其转换为 Python 等效函数。通过函数调用两个数字作为参数来显示结果。
3.将整个 JavaScript 文件转换为 Python 文件
有两种简单的方法可以将JavaScript文件转换为Python文件。
(1) 使用run_file()函数直接执行 JavaScript 文件。
import js2py
eval_result, example = js2py.run_file('example.js')
(2)转换整个JavaScript文件为Python文件。
import js2py
js2py.translate_file('example.js', 'example.py')
Python 在web 开发方面主要用于后端编码,但也可以探索一些工具在前端尝试它。
❝
文章创作不易,如果您喜欢这篇文章,请关注、点赞并分享给朋友。如有意见和建议,请在评论中反馈!
❞
先要明白的是,javascript和python都是解释型语言,它们的运行是需要具体的runtime的。
- Python: 我们最常安装的Python其实是cpython,就是基于C来运行的。除此之外还有像pypy这样的自己写了解释器的,transcrypt这种转成js之后再利用js的runtime的。基本上,不使用cpython作为python的runtime的最大问题就是通过pypi安装的那些外来包,甚至有一些cpython自己的原生包(像 collections 这种)都用不了。
- JavaScript: 常见的运行引擎有google的V8,Mozilla的SpiderMonkey等等,这些引擎会把JavaScript代码转换成机器码执行。基于这些基础的运行引擎,我们可以开发支持JS的浏览器(比如Chrome的JS运行引擎就是V8);也可以开发功能更多的JS运行环境,比如Node.js,相当于我们不需要一个浏览器,也可以跑JS代码。有了Node.js,JS包管理也变得方便许多,如果我们想把开发好的Node.js包再给浏览器用,就需要把基于Node.js的源代码编译成浏览器支持的JS代码。
在本文叙述中,假定:
- 主语言: 最终的主程序所用的语言
- 副语言: 不是主语言的另一种语言
例如,python调用js,python就是主语言,js是副语言
TL; DR
适用于:
- python和javascript的runtime(基本特指cpython[不是cython!]和Node.js)都装好了
- 副语言用了一些复杂的包(例如python用了numpy、javascript用了一点Node.js的C++扩展等)
- 对运行效率有要求的话:
- python与javascript之间的交互不能太多,传递的对象不要太大、太复杂,最好都是可序列化的对象
- javascript占的比重不过小。否则,python调js的话,启动Node.js子进程比实际跑程序还慢;js调python的话,因为js跑得快,要花很多时间在等python上。
- 因为IPC大概率会用线程同步输入输出,主语言少整啥多进程多、线程之类的并发编程
有库!有库!有库!
python调javascript
- JSPyBridge : pip install javascript优点:作者还在维护,回issue和更新蛮快的。支持比较新的python和node版本,安装简单基本支持互调用,包括绑定或者传回调函数之类的。缺点 :没有合理的销毁机制, import javascript 即视作连接JS端,会初始化所有要用的线程多线程。如果python主程序想重启对JS的连接,或者主程序用了多进程,想在每个进程都连接一次JS,都很难做到,会容易出错。
- PyExecJS : pip install PyExecJS ,比较老的技术文章都推的这个包优点: 支持除了Node.js以外的runtime,例如PhantomJS之类的缺点: End of Life,作者停止维护了
javascript调python
(因为与我的项目需求不太符合,所以了解的不太多)
- JSPyBridge : npm i pythonia
- node-python-bridge : npm install python-bridge
- python-shell : npm install python-shell
原理
首先,该方法的前提是两种语言都要有安装好的runtime,且能通过命令行调用runtime运行文件或一串字符脚本。例如,装好cpython后我们可以通过 python a.py 来运行python程序,装好Node.js之后我们可以通过 node a.js 或者 node -e "some script" 等来运行JS程序。
当然,最简单的情况下,如果我们只需要调用一次副语言,也没有啥交互(或者最多只有一次交互),那直接找个方法调用CLI就OK了。把给副语言的输入用stdin或者命令行参数传递,读取命令的输出当作副语言的输出。
例如,python可以用 subprocess.Popen , subprocess.call , subprocess.check_output 或者 os.system 之类的,Node.js可以用 child_process 里的方法, exec 或者 fork 之类的。 需要注意的是,如果需要引用其他包,Node.js需要注意在 node_modules 所在的目录下运行指令,python需要注意设置好PYTHONPATH环境变量。
# Need to set the working directory to the directory where `node_modules` resides if necessary
>>> import subprocess
>>> a, b = 1, 2
>>> print(subprocess.check_output(["node", "-e", f"console.log({a}+{b})"]))
b'3\n'
>>> print(subprocess.check_output(["node", "-e", f"console.log({a}+{b})"]).decode('utf-8'))
3
// Need to set PYTHONPATH in advance if necessary
const a = 1;
const b = 2;
const { execSync } = require("child_process");
console.log(execSync(`python -c "print(${a}+${b})"`));
//<Buffer 33 0a>
console.log(execSync(`python -c "print(${a}+${b})"`).toString());
//3
//
如果有复杂的交互,要传递复杂的对象,有的倒还可以序列化,有的根本不能序列化,咋办?
这基本要利用 进程间通信(IPC) ,通常情况下是用 管道(Pipe) 。在 stdin , stdout 和 stderr 三者之中至少挑一个建立管道。
假设我用 stdin 从python向js传数据,用 stderr 接收数据,模式大约会是这样的:
(以下伪代码仅为示意,没有严格测试过,实际使用建议直接用库)
- 新建一个副语言(假设为JS)文件 python-bridge.js :该文件不断读取 stdin 并根据发来的信息不同,进行不同的处理;同时如果需要打印信息或者传递object给主语言,将它们适当序列化后写入 stdout 或者 stderr 。process.stdin.on('data', data => { data.split('\n').forEach(line => { // Deal with each line // write message process.stdout.write(message + "\n"); // deliver object, "$j2p" can be any prefix predefined and agreed upon with the Python side // just to tell python side that this is an object needs parsing process.stderr.write("$j2p sendObj "+JSON.stringify(obj)+"\n); }); } process.on('exit', () => { console.debug('** Node exiting'); });
- 在python中,用Popen异步打开一个子进程,并将子进程的之中的至少一个,用管道连接。大概类似于:cmd = ["node", "--trace-uncaught", f"{os.path.dirname(__file__)}/python-bridge.js"] kwargs = dict( stdin=subprocess.PIPE, stdout=sys.stdout, stderr=subprocess.PIPE, ) if os.name == 'nt': kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW subproc = subprocess.Popen(cmd, **kwargs)
- 在需要调用JS,或者需要给JS传递数据的时候,往 subproc 写入序列化好的信息,写入后需要 flush ,不然可能会先写入缓冲区:subproc.stdin.write(f"$p2j call funcName {json.dumps([arg1, arg2])}".encode()) subproc.stdin.flush() # write immediately, not writing to the buffer of the stream
- 对管道化的 stdout / stderr ,新建一个线程,专门负责读取传来的数据并进行处理。是对象的重新转换成对象,是普通信息的直接打印回主进程的 stderr 或者 stdout 。def read_stderr(): while subproc.poll() is None: # when the subprocess is still alive, keep reading line = self.subproc.stderr.readline().decode('utf-8') if line.startswith('$j2p'): # receive special information _, cmd, line = line.split(' ', maxsplit=2) if cmd == 'sendObj': # For example, received an object obj = json.loads(line) else: # otherwise, write to stderr as it is sys.stderr.write(line) stderr_thread = threading.Thread(target=read_stderr, args=(), daemon=True) stderr_thread.start()这里由于我们的 stdout 没有建立管道,所以node那边往 stdout 里打印的东西会直接打印到python的 sys.stdout 里,不用自己处理。
- 由于线程是异步进行的,什么时候知道一个函数返回的对象到了呢?答案是用线程同步手段,信号量(Semaphore)、条件(Condition),事件(Event)等等,都可以。以 python的条件 为例:func_name_cv = threading.Condition() # use a flag and a result object in case some function has no result func_name_result_returned = False func_name_result = None def func_name_wrapper(arg1, arg2): # send arguments subproc.stdin.write(f"$p2j call funcName {json.dumps([arg1, arg2])}".encode()) subproc.stdin.flush() # wait for the result with func_name_cv: if not func_name_result_returned: func_name_cv.wait(timeout=10000) # when result finally returned, reset the flag func_name_result_returned = False return func_name_result同时,需要在读stderr的线程 read_stderr 里解除对这个返回值的阻塞。需要注意的是,如果JS端因为意外而退出了, subproc 也会死掉, 这时候也要记得取消主线程中的阻塞 。def read_stderr(): while subproc.poll() is None: # when the subprocess is still alive, keep reading # Deal with a line line = self.subproc.stderr.readline().decode('utf-8') if line.startswith('$j2p'): # receive special information _, cmd, line = line.split(' ', maxsplit=2) if cmd == 'sendObj': # acquire lock here to ensure the editing of func_name_result is mutex with func_name_cv: # For example, received an object func_name_result = json.loads(line) func_name_result_returned = True # unblock func_name_wrapper when receiving the result func_name_cv.notify() else: # otherwise, write to stderr as it is sys.stderr.write(line) # If subproc is terminated (mainly due to error), still need to unblock func_name_wrapper func_name_cv.notify()当然这是比较简单的版本,由于对JS的调用基本都是线性的,所以可以知道只要得到一个object的返回,那就一定是 func_name_wrapper 对应的结果。如果函数多起来的话,情况会更复杂。
- 如果想 取消对JS的连接 ,首先应该先关闭子进程,然后等待读 stdout / stderr 的线程自己自然退出,最后 一定不要忘记关闭管道 。并且 这三步的顺序不能换 ,如果先关了管道,读线程会因为 stdout / stderr 已经关了而出错。subproc.terminate() stderr_thread.join() subproc.stdin.close() subproc.stderr.close()
如果是通过这种原理javascript调用python,方法也差不多,javascript方是Node.js的话,用的是 child_process 里的指令。
优点
- 只需要正常装好两方的runtime就能实现交互,运行环境相对比较好配。
- 只要python方和javascript方在各自的runtime里正常运行没问题,那么连上之后运行也基本不会有问题。(除非涉及并发)
- 对两种语言的所有可用的扩展包基本都能支持。
缺点
- 当python与JavaScript交互频繁,且交互的信息都很大的时候,可能会很影响程序效率。因为仅仅通过最多3个管道混合处理普通要打印的信息、python与js交互的对象、函数调用等,通信开销很大。
- 要另起一个子进程运行副语言的runtime,会花一定时间和空间开销。