Як викликати функцію Python з Node.js


208

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


4
вузол-пітон . Ніколи не використовував його сам, хоча.
univerio

22
Через два роки, node-pythonздається, це покинутий проект.
імрек


Дивіться також 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
});

Оскільки це дозволяє передавати декілька аргументів до сценарію за допомогою нересту, ви можете реструктурувати сценарій python так, що один з аргументів вирішує, яку функцію викликати, а інший аргумент передається цій функції тощо.

Сподіваюся, це було зрозуміло. Повідомте мене, чи потрібно щось уточнення.


17
@ PauloS.Abreu: Проблема, з якою у мене виникає, execполягає в тому, що він повертає буфер замість потоку, і якщо ваші дані перевищують maxBufferналаштування, яке за замовчуванням становить 200 кБ, ви отримуєте буфер перевищений виняток і ваш процес загине. Оскільки spawnвикористовує потоки, він більш гнучкий, ніж exec.
NeverForgetY2K

2
Лише невелика примітка, якщо ви використовуєте вузол, ви, ймовірно, не повинні використовувати ключове слово процес
alexvicegrab

2
Як мені встановити зовнішні залежності від піп? Мені потрібен numpy для проекту, і я не можу змусити його запускатись, оскільки його не встановлено.
javiergarval

2
@javiergarval Це краще відповідатиме новим питанням, а не коментарем.
NeverForgetY2K

3
чи є інший спосіб повернути дані з python, окрім друку? Мій скрипт python видає багато даних журналу, і, мабуть, у нього проблеми зі
стиранням

111

Приклад для людей, які перебувають у фоновому режимі 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 & obeces.
Майк Вільямсон

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
Ця проблема не дозволяє мені використовувати її - github.com/extrabacon/python-shell/isissue/179
mhlavacka

1
Якщо ви отримуєте цю помилку - TypeError: PythonShell.run не є функцією, тоді переконайтесь, що ви імпортуєте її так, як цей var {PythonShell} = вимагає ('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

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 ("дані"), це не правильний спосіб зробити це, оскільки якщо ви отримаєте багато даних, ви отримаєте лише першу частину. Натомість ви повинні зробити це в кінцевій події.

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")

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 вузла не завантажує декілька запитів, які мені потрібно зробити


У мене є сервер створення сервера на nodejs, і у мене є декілька пов'язаних з машинним навчанням сценаріїв python, які я створюю за допомогою породу дочірнього процесу через nodejs, коли я отримую запит на своєму сервері nodejs. Як запропоновано в цій темі. Моє запитання: чи це правильний спосіб зробити це, або я можу зробити свій скрипт python таким чином, як служба flask прив'язується до порту за допомогою zmq та виконувати обіцянку від nodejs до цієї служби. Я правильно маю на увазі, який спосіб є збереженням пам’яті та методом економії швидкості?
Асвін

1
Напевно, ви хочете, щоб матеріали python працювали незалежно. Ви не хочете залежностей від жорстких кодів, особливо для чогось складнішого, як сервіс ml. Що робити, якщо ви хочете додати ще одну частину до цієї архітектури? Як шар кешування перед ml, або спосіб додавання додаткових параметрів до моделі ml? Це також пам'ять для запуску сервера python, але, напевно, вам знадобиться гнучкість. Пізніше ви можете розділити ці шматки на два сервери
1mike12

Він запитав, чи може він викликати функцію , це не відповідає на запитання.
K - Токсичність в SO зростає.

2
@ 1mike12 "karl_morrison_is_a_pedant ()" ха-ха, полюби його!
K - Токсичність в SO зростає.

0

Ви можете взяти пітон, перекласти його, а потім назвати його так, як ніби JavaScript. Я зробив це успішно для screeps і навіть змусив його працювати в браузері a la brython .


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 знаходиться у папці вашого проекту.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.