跳至正文

NodeJS 使用 PythonShell 和 Python 通信

为了实现之后的一个大项目,测试了一下 NodeJS 和 Python 的通信方式,采用 PythonShell 现实。

PythonShell

参考文章:

How to Execute Python Scripts in Electron and NodeJS

python-shell node, keep a python script running

描述如下:

通过 Electron 运行一个 HTML 页面,上面有两个按钮,分别是 Sit 和 Roll Over;

在 Python 中定义一个 Dog 类,定了两个方法,sit 和 roll_over,其中 roll_over 可接受一个参数,让小狗打滚几次;动作结束之后将结果通知给 HTML 页面;

先来简单的 Python 端:

dog.py

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def sit(self):
        print(f'{self.name.title()} is now sitting.')

    def roll_over(self, count):
        print(f'{self.name.title()} rolled over {count} times!')

main.py

import json
from random import randint

from dog import Dog

def main():
    rnd = randint(1, 6)
    my_dog = Dog('Youyou' + str(rnd), 3)

    while True:
        inp = input(' ')
        data = json.loads(inp)
        command = data['command']

        if command == 'do-sit':
            my_dog.sit()
        elif command == 'do-roll-over':
            my_dog.roll_over(data['value'])
        else:
            print('??')

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('error')

这里有一个问题要说明一下:对于我这种简单的需求,完全不必要把 Python 程序弄这么复杂啊?!完全可以这么写

import sys, json

from dog import Dog

my_dog = Dog('Youyou', 3)

data = sys.stdin.readlines()
data = json.loads(data[0])

if data['command'] == 'do-sit':
    my_dog.sit()
elif data['command'] == 'do-roll-over':
    my_dog.roll_over(data['value'])

之所以没有这么去弄,是要为之后的项目考虑,这个 Python 程序不可能每次都执行,结束,执行,结束……;假设这个 Python 需要访问网络获取一些前期数据(或者和其他设备联通),那么每次都得创建连接;所以采用了上面复杂的写法,其中每次运行 Python 程序,都在小狗名字后面加了一个随机数,来模拟一次性。

OK,那么 Python 的工作做完了,接下来是比较复杂的部分 Electron:

main.js

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

const { PythonShell } = require('python-shell')

let mainWindow, pyshell

const createWindow = () => {
    mainWindow = new BrowserWindow({
        width: 1024,
        height: 768,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })

    pyshell = new PythonShell('main.py')

    pyshell.on('message', (message) => {
        console.log(message)
    
        // 通过 preload.js 将结果发送给 renderer.js
        mainWindow.webContents.send('to-renderer', message)
    })

    // 通过 preload.js 监听来自 renderer.js 的消息
    ipcMain.on('from-renderer', (event, data) => {
        // 上面的 event 一定要有,因为这是一个 IpcMainEvent

        pyshell.send(JSON.stringify(data))
    })

    mainWindow.loadFile('index.html')
    mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
    createWindow()

    app.on('activate', () => {
        if(BrowserWindow.getAllWindows().length === 0) createWindow()
    })
})

app.on('window-all-closed', () => {
    if(process.platform !== 'darwin') app.quit()
})

preload.js

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    fromRenderer: (data) => {
        ipcRenderer.send('from-renderer', data)
    },
    toRenderer: (callback) => {
        ipcRenderer.on('to-renderer', callback)
    }
})

renderer.js

document.addEventListener('DOMContentLoaded', () => {
    console.log('Ready')

    if(window.electronAPI) {
        window.electronAPI.toRenderer((event, value) => {
            console.log(value)
        })
    }

    const fooBtn = document.getElementById('btn-sit')
    fooBtn.addEventListener('click', () => {
        if(window.electronAPI) {
            window.electronAPI.fromRenderer({
                command: 'do-sit'
            })
        }
    })

    const barBtn = document.getElementById('btn-roll-over')
    barBtn.addEventListener('click', () => {
        if(window.electronAPI) {
            window.electronAPI.fromRenderer({
                command: 'do-roll-over',
                value: 2
            })
        }
    })
})

Electron 里面注意的问题比较多,首先要通过 npm 安装 elecron 和 python-shell

npm i electron
npm i python-shell

如果安装 electron 碰到问题,请参考官方文章或参考我之前的文章;安装 python-shell 应该没有任何问题。

其次,推荐在 main.js 中运行 PythonShell;当然你也可以在 renderer.js 中通过 require 引入 PythonShell 来运行,但是官方不推荐(参考文章),还是建议通过 preload.js 来实现。

至此这个简单的 Demo 就实现了,WOW

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注