热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python与Javascript相互调用超详细讲解(2022年1月最新)(一)基本原理Part1通过子进程和进程间通信(IPC)

首先要明白的是,javascript和python都是解释型语言,它们的运行是需要具体的runtime的。Python:我们最常安装的Python其实是cpython,就是基于C来

首先要明白的是,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

适用于:



  1. python和Javascript的runtime(基本特指cpython[不是cython!]和Node.js)都装好了

  2. 副语言用了一些复杂的包(例如python用了numpy、Javascript用了一点Node.js的C++扩展等)

  3. 对运行效率有要求的话:

    • python与Javascript之间的交互不能太多,传递的对象不要太大、太复杂,最好都是可序列化的对象

    • Javascript占的比重不过小。否则,python调js的话,启动Node.js子进程比实际跑程序还慢;js调python的话,因为js跑得快,要花很多时间在等python上。



  4. 因为IPC大概率会用线程同步输入输出,主语言少整啥多进程多、线程之类的并发编程

有库!有库!有库!


python调Javascript



  • JSPyBridge: pip install Javascript

    • 优点:

      1. 作者还在维护,回issue和更新蛮快的。

      2. 支持比较新的python和node版本,安装简单

      3. 基本支持互调用,包括绑定或者传回调函数之类的。



    • 缺点:没有合理的销毁机制,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.Popensubprocess.callsubprocess.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})"`));
//
console.log(execSync(`python -c "print(${a}+${b})"`).toString());
//3
//

如果有复杂的交互,要传递复杂的对象,有的倒还可以序列化,有的根本不能序列化,咋办?

这基本要利用进程间通信(IPC),通常情况下是用管道(Pipe)。在stdinstdoutstderr三者之中至少挑一个建立管道。

假设我用stdin从python向js传数据,用stderr接收数据,模式大约会是这样的:

(以下伪代码仅为示意,没有严格测试过,实际使用建议直接用库)



  1. 新建一个副语言(假设为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');
    });


  2. 在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)


  3. 在需要调用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


  4. 对管道化的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里,不用自己处理。


  5. 由于线程是异步进行的,什么时候知道一个函数返回的对象到了呢?答案是用线程同步手段,信号量(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对应的结果。如果函数多起来的话,情况会更复杂。


  6. 如果想取消对JS的连接,首先应该先关闭子进程,然后等待读stdout/stderr的线程自己自然退出,最后一定不要忘记关闭管道。并且这三步的顺序不能换,如果先关了管道,读线程会因为stdout/stderr已经关了而出错。

    subproc.terminate()
    stderr_thread.join()
    subproc.stdin.close()
    subproc.stderr.close()


如果是通过这种原理Javascript调用python,方法也差不多,Javascript方是Node.js的话,用的是child_process里的指令。


优点

  1. 只需要正常装好两方的runtime就能实现交互,运行环境相对比较好配。

  2. 只要python方和Javascript方在各自的runtime里正常运行没问题,那么连上之后运行也基本不会有问题。(除非涉及并发)

  3. 对两种语言的所有可用的扩展包基本都能支持。


缺点

  1. 当python与Javascript交互频繁,且交互的信息都很大的时候,可能会很影响程序效率。因为仅仅通过最多3个管道混合处理普通要打印的信息、python与js交互的对象、函数调用等,通信开销很大。

  2. 要另起一个子进程运行副语言的runtime,会花一定时间和空间开销。



推荐阅读
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
author-avatar
宝丁2502907973
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有