Cómo llamar a una función de Python desde Node.js

209

Tengo una aplicación Express Node.js, pero también tengo un algoritmo de aprendizaje automático para usar en Python. ¿Hay alguna manera de llamar a las funciones de Python desde mi aplicación Node.js para aprovechar el poder de las bibliotecas de aprendizaje automático?

Genjuro
fuente
44
nodo-python . Sin embargo, nunca lo usé.
Univerio
23
Dos años después, node-pythonparece ser un proyecto abandonado.
Imrek
Consulte también github.com/QQuick/Transcrypt para compilar Python en JavaScript y luego invocarlo
Jonathan

Respuestas:

262

La forma más fácil que conozco es usar el paquete "child_process" que viene empaquetado con el nodo.

Entonces puedes hacer algo como:

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

Entonces, todo lo que tiene que hacer es asegurarse de que está import sysen su secuencia de comandos de Python, y luego puede acceder arg1usando sys.argv[1], arg2usando sys.argv[2], etc.

Para enviar datos de vuelta al nodo, simplemente haga lo siguiente en el script de Python:

print(dataToSendBack)
sys.stdout.flush()

Y luego el nodo puede escuchar los datos usando:

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

Dado que esto permite que se pasen múltiples argumentos a un script usando spawn, puede reestructurar un script de python para que uno de los argumentos decida qué función llamar, y el otro argumento pase a esa función, etc.

Espero que esto esté claro. Avísame si algo necesita aclaración.

NeverForgetY2K
fuente
17
@ PauloS.Abreu: El problema que tengo execes que devuelve un búfer en lugar de una secuencia, y si sus datos exceden la maxBufferconfiguración, que por defecto es 200kB, obtiene una excepción de búfer excedido y su proceso se anula. Dado que spawnutiliza secuencias, es más flexible que exec.
NeverForgetY2K
2
Solo una pequeña nota, si usa nodo, probablemente no debería usar la palabra clave de proceso
alexvicegrab
2
¿Cómo debo instalar las dependencias externas de pip? Necesito numpy para un proyecto y no puedo hacer que se ejecute porque no lo tiene instalado.
javiergarval
2
@javiergarval Eso sería más adecuado como una nueva pregunta en lugar de un comentario.
NeverForgetY2K
3
¿Hay alguna otra forma de devolver datos de Python que no sea imprimiendo? Mi script en Python da salida a una gran cantidad de datos de registro y al parecer tiene problemas para el lavado de todos esos datos
lxknvlk
112

Ejemplo para personas que provienen de Python y desean integrar su modelo de aprendizaje automático en la aplicación Node.js:

Utiliza el child_processmódulo central:

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

No requiere sysmódulo en su script Python.

A continuación se muestra una forma más modular de realizar la tarea usando 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!'))
Amit Upadhyay
fuente
8
Me sorprende que esto no haya obtenido más votos. Si bien la respuesta de @ NeverForgetY2K está bien, esta respuesta contiene un ejemplo más detallado que incluye la escucha del puerto, y utiliza muy bien las convenciones JS más modernas como const & promises.
Mike Williamson el
2
Gran ejemplo La promesa uno fue buena para detectar algunos errores que tuve en el script de Python.
htafoya
38

El python-shellmódulo by extrabacones una manera simple de ejecutar scripts Python desde Node.js con una comunicación entre procesos básica pero eficiente y un mejor manejo de errores.

Instalación: npm install python-shell .

Ejecutando un script simple de Python:

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

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

Ejecutar un script de Python con argumentos y opciones:

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

Para obtener la documentación completa y el código fuente, consulte https://github.com/extrabacon/python-shell

Soumik Rakshit
fuente
3
Este problema me impide usarlo - github.com/extrabacon/python-shell/issues/179
mhlavacka
1
Si obtiene este error: TypeError: PythonShell.run no es una función. Luego, asegúrese de importarlo así var {PythonShell} = require ('python-shell');
Mohammed
4

Ahora puede usar bibliotecas RPC que admiten Python y Javascript, como zerorpc

Desde su portada:

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

Servidor 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()
Geordie
fuente
También puede usar socket.io tanto en el lado del nodo como en el de Python.
Bruno Gabuzomeu
3

La mayoría de las respuestas anteriores llaman al éxito de la promesa en el encendido ("datos"), no es la forma correcta de hacerlo porque si recibe muchos datos solo obtendrá la primera parte. En su lugar, debes hacerlo al final del evento.

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;

Llamada:

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

pitón:

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

Estoy en el nodo 10 y el proceso hijo 1.0.2. Los datos de python son una matriz de bytes y deben convertirse. Solo otro ejemplo rápido de hacer una solicitud http en 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 no es un ejemplo artificial ya que el módulo http del nodo no carga algunas solicitudes que necesito hacer

1mike12
fuente
Tengo una compilación de servidor back-end en nodejs y tengo pocos scripts de Python relacionados con el aprendizaje automático que genero usando el proceso hijo generado a través de nodejs cada vez que recibo la solicitud en mi servidor de nodejs. Como se sugiere en este hilo. Mi pregunta es, ¿es esta la forma correcta de hacerlo o puedo hacer que mi script de Python se ejecute como un servicio de matraz enlazado a un puerto usando zmq y ejecutar una promesa de nodejs a este servicio? Por cierto, lo que quiero decir es, ¿de qué manera se ahorra memoria y se ahorra velocidad?
Aswin
1
Probablemente quieras que las cosas de Python se ejecuten de forma independiente. No desea dependencias de códigos duros, especialmente para algo más complicado como un servicio ml. ¿Y si quisieras agregar otra pieza a esta arquitectura? ¿Como una capa de almacenamiento en caché frente al ml o una forma de agregar parámetros adicionales al modelo ml? También es memoria ejecutar un servidor Python, pero probablemente necesitará la flexibilidad. Más adelante, puede separar las dos piezas en dos servidores
1mike12
Preguntó si podía llamar a una función , esto no responde la pregunta.
K - La toxicidad en SO está creciendo.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" ¡jaja me encanta amigo!
K - La toxicidad en SO está creciendo.
0

Puede tomar su python, transpilarlo y luego llamarlo como si fuera javascript. Lo he hecho con éxito para screeps e incluso lo ejecuté en el navegador a la brython .

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

Esto funcionó para mí. Su python.exe debe agregarse a sus variables de ruta para este fragmento de código. Además, asegúrese de que su script de Python esté en la carpeta de su proyecto.

nas_levente
fuente