Come chiamare una funzione Python da Node.js


209

Ho un'applicazione Express Node.js, ma ho anche un algoritmo di apprendimento automatico da utilizzare in Python. Esiste un modo per chiamare le funzioni Python dalla mia applicazione Node.js per sfruttare la potenza delle librerie di machine learning?


4
nodo-pitone . Non l'ho mai usato da solo, però.
univerio,

23
Due anni dopo, node-pythonsembra essere un progetto abbandonato.
imrek,


Vedi anche github.com/QQuick/Transcrypt per compilare Python in javascript e poi invocarlo
Jonathan

Risposte:


262

Il modo più semplice che conosco è usare il pacchetto "child_process" che viene fornito con il nodo.

Quindi puoi fare qualcosa del tipo:

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

Quindi tutto quello che devi fare è assicurarti di essere import sysnel tuo script Python, e quindi puoi accedere arg1usando sys.argv[1], arg2usando sys.argv[2]e così via.

Per rimandare i dati al nodo basta fare quanto segue nello script python:

print(dataToSendBack)
sys.stdout.flush()

E quindi il nodo può ascoltare i dati usando:

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

Poiché ciò consente a più argomenti di essere passati a uno script tramite spawn, è possibile ristrutturare uno script Python in modo che uno degli argomenti decida quale funzione chiamare e l'altro argomento venga passato a quella funzione, ecc.

Spero fosse chiaro. Fammi sapere se qualcosa ha bisogno di chiarimenti.


17
@ PauloS.Abreu: Il problema che ho execè che restituisce un buffer anziché uno stream e se i tuoi dati superano l' maxBufferimpostazione, che per impostazione predefinita è 200kB, ottieni un buffer che ha superato l'eccezione e il processo viene interrotto. Poiché spawnutilizza flussi, è più flessibile di exec.
NeverForgetY2K

2
Solo una piccola nota, se usi il nodo probabilmente non dovresti usare la parola chiave process
alexvicegrab,

2
Come devo installare dipendenze pip esterne? Ho bisogno di intorpidire un progetto e non riesco a farlo funzionare perché non lo ha installato.
javiergarval,

2
@javiergarval Sarebbe più adatto come nuova domanda invece che come commento.
NeverForgetY2K

3
c'è un altro modo per restituire dati da Python se non quello di stampare? Il mio script Python genera molti dati di log e apparentemente ha problemi a
scaricare

112

Esempio per persone che provengono da Python e desiderano integrare il proprio modello di apprendimento automatico nell'applicazione Node.js:

Utilizza il child_processmodulo principale:

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!'))

Non richiede sysmodulo nel tuo script Python.

Di seguito è riportato un modo più modulare di eseguire l'attività utilizzando 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
Sono sorpreso che questo non abbia ottenuto voti più alti. Mentre la risposta di @ NeverForgetY2K va bene, questa risposta contiene un esempio più dettagliato che include l'ascolto della porta e utilizza piacevolmente le convenzioni JS più moderne come const & promes.
Mike Williamson,

2
Ottimo esempio Promesso che uno era buono per rilevare alcuni errori che ho avuto sullo script Python.
htafoya,

38

Il python-shellmodulo extrabaconè un modo semplice per eseguire script Python da Node.js con comunicazioni tra processi di base, ma efficienti e una migliore gestione degli errori.

Installazione: npm install python-shell .

Esecuzione di un semplice script Python:

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

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

Esecuzione di uno script Python con argomenti e opzioni:

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);
});

Per la documentazione completa e il codice sorgente, consultare https://github.com/extrabacon/python-shell


3
Questo problema mi impedisce di usarlo - github.com/extrabacon/python-shell/issues/179
mhlavacka,

1
Se ricevi questo errore - TypeError: PythonShell.run non è una funzione Quindi assicurati di importarlo in questo modo {PythonShell} = require ('python-shell');
Mohammed,

4

Ora puoi usare le librerie RPC che supportano Python e Javascript come zerorpc

Dalla loro prima pagina:

Client 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()

Puoi anche usare socket.io sia sul lato Node che su quello Python.
Bruno Gabuzomeu,

3

La maggior parte delle risposte precedenti chiama il successo della promessa in on ("dati"), non è il modo corretto di farlo perché se ricevi molti dati otterrai solo la prima parte. Invece devi farlo sull'evento finale.

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;

Chiamata:

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

pitone:

try:
    argu = json.loads(sys.argv[1])
except:
    raise Exception("error while loading argument")

2

Sono sul nodo 10 e processo figlio 1.0.2. I dati di Python sono un array di byte e devono essere convertiti. Solo un altro rapido esempio di come effettuare una richiesta http in python.

nodo

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 non è un esempio inventato poiché il modulo http del nodo non carica alcune richieste che devo fare


Ho un backend server basato su nodejs e ho pochi script Python relativi all'apprendimento automatico che ho generato usando il processo figlio generato tramite nodejs ogni volta che ricevo la richiesta sul mio server nodejs. Come suggerito in questa discussione. La mia domanda è: è questo il modo giusto di farlo o posso avere il mio script Python eseguito come un servizio di pallone collegato a una porta usando zmq ed eseguire una promessa da nodejs a questo servizio. Di diritto, ciò che intendo è, in che modo il metodo di salvataggio della memoria e di velocità?
Aswin,

1
Probabilmente vuoi che le cose di Python funzionino indipendentemente. Non vuoi dipendenze da hard code, specialmente per qualcosa di più complicato come un servizio ml. E se volessi aggiungere un altro pezzo a questa architettura? Come uno strato di cache davanti al ml o un modo per aggiungere parametri extra al modello ml? È memoria per eseguire anche un server Python, ma probabilmente avrai bisogno della flessibilità. Successivamente puoi separare i due pezzi su due server
1mike

Ha chiesto se poteva chiamare una funzione , questo non risponde alla domanda.
K - La tossicità in SO sta crescendo.

2
@ 1mike12 "karl_morrison_is_a_pedant ()" haha ​​lo adoro amico!
K - La tossicità in SO sta crescendo.

0

Potresti prendere il tuo pitone, traspilarlo e quindi chiamarlo come se fosse javascript. L'ho fatto con successo per i brividi e l'ho persino fatto funzionare nel browser alla 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);
}); 

Questo ha funzionato per me. Il tuo python.exe deve essere aggiunto alle variabili di percorso per questo frammento di codice. Inoltre, assicurati che lo script python sia nella cartella del progetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.