Как вызвать функцию Python из Node.js

209

У меня есть приложение Express Node.js, но у меня также есть алгоритм машинного обучения для использования в Python. Есть ли способ вызвать функции Python из моего приложения Node.js, чтобы использовать возможности библиотек машинного обучения?

Genjuro
источник
4
узел-питон . Никогда не использовал это сам, все же.
univerio
23
Два года спустя, node-pythonпохоже, заброшенный проект.
imrek
Смотрите также github.com/QQuick/Transcrypt для компиляции python в javascript и последующего его вызова
Джонатан

Ответы:

262

Самый простой способ, который я знаю, это использовать пакет child_process, который поставляется вместе с узлом.

Тогда вы можете сделать что-то вроде:

const spawn = require("child_process").spawn;
const pythonProcess = spawn('python',["path/to/script.py", arg1, arg2, ...]);

Затем все, что вам нужно сделать, это убедиться, что вы import sysв своем скрипте Python, и тогда вы можете получить доступ arg1с помощью sys.argv[1], arg2с помощью sys.argv[2], и так далее.

Чтобы отправить данные обратно на узел, просто сделайте следующее в скрипте python:

print(dataToSendBack)
sys.stdout.flush()

И тогда узел может прослушивать данные, используя:

pythonProcess.stdout.on('data', (data) => {
    // Do something with the data returned from python script
});

Поскольку это позволяет нескольким аргументам передаваться в скрипт с помощью spawn, вы можете реструктурировать скрипт на python так, чтобы один из аргументов решал, какую функцию вызывать, а другой аргумент был передан этой функции и т. Д.

Надеюсь, это было ясно. Дайте мне знать, если что-то нужно уточнить.

NeverForgetY2K
источник
17
@ PauloS.Abreu: Проблема, с execкоторой я столкнулся, заключается в том, что он возвращает буфер вместо потока, и если ваши данные превышают maxBufferнастройку, которая по умолчанию составляет 200 КБ, вы получаете исключение превышения буфера, и ваш процесс завершается. Так как spawnиспользует потоки, он более гибкий, чем exec.
NeverForgetY2K
2
Просто небольшая заметка: если вы используете узел, вы, вероятно, не должны использовать ключевое слово процесса
alexvicegrab
2
Как мне установить внешние пип-зависимости? Мне нужен NumPy для проекта, и я не могу заставить его работать, потому что он не установлен.
Javiergarval
2
@javiergarval Это было бы лучше, чем новый комментарий вместо нового вопроса.
NeverForgetY2K
3
Есть ли другой способ вернуть данные из Python, кроме печати? Мой скрипт на python выводит много данных журнала и, по-видимому, у него возникают проблемы со сбросом всех этих данных
lxknvlk
112

Пример для людей, которые имеют опыт работы с Python и хотят интегрировать свою модель машинного обучения в приложение Node.js:

Он использует child_processосновной модуль:

const express = require('express')
const app = express()

app.get('/', (req, res) => {

    const { spawn } = require('child_process');
    const pyProg = spawn('python', ['./../pypy.py']);

    pyProg.stdout.on('data', function(data) {

        console.log(data.toString());
        res.write(data);
        res.end('end');
    });
})

app.listen(4000, () => console.log('Application listening on port 4000!'))

Это не требует sysмодуля в вашем скрипте Python.

Ниже приведен более модульный способ выполнения задачи с использованием Promise:

const express = require('express')
const app = express()

let runPy = new Promise(function(success, nosuccess) {

    const { spawn } = require('child_process');
    const pyprog = spawn('python', ['./../pypy.py']);

    pyprog.stdout.on('data', function(data) {

        success(data);
    });

    pyprog.stderr.on('data', (data) => {

        nosuccess(data);
    });
});

app.get('/', (req, res) => {

    res.write('welcome\n');

    runPy.then(function(fromRunpy) {
        console.log(fromRunpy.toString());
        res.end(fromRunpy);
    });
})

app.listen(4000, () => console.log('Application listening on port 4000!'))
Амит Упадхяй
источник
8
Я удивлен, что это не набрало больше голосов. Хотя ответ @ NeverForgetY2K в порядке, этот ответ содержит более подробный пример, включая прослушивание порта, и прекрасно использует более современные соглашения JS, такие как const & promises.
Майк Уильямсон
2
Отличный пример. Обещание было хорошо обнаружить некоторые ошибки, которые у меня были в скрипте Python.
Хтафоя
38

python-shellМодуль с помощью extrabaconпростой способ запустить Python скрипты из Node.js с основным, но эффективная связь между процессами и лучше обработки ошибок.

Установка: npm install python-shell .

Запуск простого скрипта Python:

var PythonShell = require('python-shell');

PythonShell.run('my_script.py', function (err) {
  if (err) throw err;
  console.log('finished');
});

Запуск скрипта Python с аргументами и параметрами:

var PythonShell = require('python-shell');

var options = {
  mode: 'text',
  pythonPath: 'path/to/python',
  pythonOptions: ['-u'],
  scriptPath: 'path/to/my/scripts',
  args: ['value1', 'value2', 'value3']
};

PythonShell.run('my_script.py', options, function (err, results) {
  if (err) 
    throw err;
  // Results is an array consisting of messages collected during execution
  console.log('results: %j', results);
});

Для полной документации и исходного кода, проверьте https://github.com/extrabacon/python-shell

Сумик Ракшит
источник
3
Эта проблема
мешает
1
Если вы получаете эту ошибку - TypeError: PythonShell.run не является функцией. Затем убедитесь, что вы импортируете ее следующим образом: var {PythonShell} = require ('python-shell');
Мухаммед
4

Теперь вы можете использовать библиотеки RPC, которые поддерживают Python и Javascript, такие как zerorpc

С их главной страницы:

Клиент Node.js

var zerorpc = require("zerorpc");

var client = new zerorpc.Client();
client.connect("tcp://127.0.0.1:4242");

client.invoke("hello", "RPC", function(error, res, more) {
    console.log(res);
});

Python Server

import zerorpc

class HelloRPC(object):
    def hello(self, name):
        return "Hello, %s" % name

s = zerorpc.Server(HelloRPC())
s.bind("tcp://0.0.0.0:4242")
s.run()
Джорди
источник
Вы также можете использовать socket.io как на стороне Node, так и на стороне Python.
Бруно Габузомеу
3

Большинство предыдущих ответов называют успешное обещание в on («data»), это неправильный способ сделать это, потому что, если вы получите много данных, вы получите только первую часть. Вместо этого вы должны сделать это в конце мероприятия.

const { spawn } = require('child_process');
const pythonDir = (__dirname + "/../pythonCode/"); // Path of python script folder
const python = pythonDir + "pythonEnv/bin/python"; // Path of the Python interpreter

/** remove warning that you don't care about */
function cleanWarning(error) {
    return error.replace(/Detector is not able to detect the language reliably.\n/g,"");
}

function callPython(scriptName, args) {
    return new Promise(function(success, reject) {
        const script = pythonDir + scriptName;
        const pyArgs = [script, JSON.stringify(args) ]
        const pyprog = spawn(python, pyArgs );
        let result = "";
        let resultError = "";
        pyprog.stdout.on('data', function(data) {
            result += data.toString();
        });

        pyprog.stderr.on('data', (data) => {
            resultError += cleanWarning(data.toString());
        });

        pyprog.stdout.on("end", function(){
            if(resultError == "") {
                success(JSON.parse(result));
            }else{
                console.error(`Python error, you can reproduce the error with: \n${python} ${script} ${pyArgs.join(" ")}`);
                const error = new Error(resultError);
                console.error(error);
                reject(resultError);
            }
        })
   });
}
module.exports.callPython = callPython;

Вызов:

const pythonCaller = require("../core/pythonCaller");
const result = await pythonCaller.callPython("preprocessorSentiment.py", {"thekeyYouwant": value});

питон:

try:
    argu = json.loads(sys.argv[1])
except:
    raise Exception("error while loading argument")
bormat
источник
2

Я на узле 10 и дочерний процесс 1.0.2. Данные из python являются байтовым массивом и должны быть преобразованы. Еще один быстрый пример выполнения http-запроса на python.

узел

const process = spawn("python", ["services/request.py", "https://www.google.com"])

return new Promise((resolve, reject) =>{
    process.stdout.on("data", data =>{
        resolve(data.toString()); // <------------ by default converts to utf-8
    })
    process.stderr.on("data", reject)
})

request.py

import urllib.request
import sys

def karl_morrison_is_a_pedant():   
    response = urllib.request.urlopen(sys.argv[1])
    html = response.read()
    print(html)
    sys.stdout.flush()

karl_morrison_is_a_pedant()

ps не надуманный пример, так как модуль http узла не загружает несколько запросов, которые мне нужно сделать

1mike12
источник
У меня есть серверная серверная сборка на nodejs, и у меня есть несколько связанных с машинным обучением скриптов python, которые я порождаю, используя дочерний процесс, порождаемый через nodejs всякий раз, когда я получаю запрос на моем сервере nodejs. Как предлагается в этой теме. Мой вопрос заключается в том, является ли это правильным способом сделать это, или я могу запустить свой скрипт на python, как службу фляги, привязанную к порту с помощью zmq, и запустить обещание от nodejs для этой службы. По праву я имею в виду, каким образом экономится память и метод экономии скорости?
Асвин
1
Вы, вероятно, хотите, чтобы Python работал независимо. Вам не нужны зависимости жесткого кода, особенно для чего-то более сложного, например, службы ml. Что, если вы хотите добавить еще одну часть к этой архитектуре? Как кеширующий слой перед ml или способ добавить дополнительные параметры в ml? Это также память для запуска сервера Python, но вам, вероятно, понадобится гибкость. Позже вы можете разделить две части на два сервера
1mike12
Он спросил, может ли он вызвать функцию , это не отвечает на вопрос.
К - Токсичность в СО растет.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" хаха, приятель!
К - Токсичность в СО растет.
0

Вы можете взять свой питон, перенести его, а затем назвать так, как если бы это был JavaScript. Я сделал это успешно для screeps и даже заставил его работать в браузере по выбору .

Джонатан
источник
0
/*eslint-env es6*/
/*global require*/
/*global console*/
var express = require('express'); 
var app = express();

// Creates a server which runs on port 3000 and  
// can be accessed through localhost:3000
app.listen(3000, function() { 
    console.log('server running on port 3000'); 
} ) 

app.get('/name', function(req, res) {

    console.log('Running');

    // Use child_process.spawn method from  
    // child_process module and assign it 
    // to variable spawn 
    var spawn = require("child_process").spawn;   
    // Parameters passed in spawn - 
    // 1. type_of_script 
    // 2. list containing Path of the script 
    //    and arguments for the script  

    // E.g : http://localhost:3000/name?firstname=Levente
    var process = spawn('python',['apiTest.py', 
                        req.query.firstname]);

    // Takes stdout data from script which executed 
    // with arguments and send this data to res object
    var output = '';
    process.stdout.on('data', function(data) {

        console.log("Sending Info")
        res.end(data.toString('utf8'));
    });

    console.log(output);
}); 

Это сработало для меня. Ваш python.exe должен быть добавлен к вам переменные пути для этого фрагмента кода. Также убедитесь, что ваш скрипт на python находится в папке вашего проекта.

nas_levente
источник