Comment appeler une fonction Python à partir de Node.js

209

J'ai une application Express Node.js, mais j'ai également un algorithme d'apprentissage machine à utiliser en Python. Existe-t-il un moyen d'appeler des fonctions Python à partir de mon application Node.js pour utiliser la puissance des bibliothèques d'apprentissage automatique?

Genjuro
la source
4
nœud-python . Je ne l'ai jamais utilisé moi-même.
univerio
23
Deux ans plus tard, node-pythonsemble être un projet abandonné.
imrek
Voir aussi github.com/QQuick/Transcrypt pour compiler python en javascript puis l'invoquer
Jonathan

Réponses:

262

Le moyen le plus simple que je connaisse est d'utiliser le package "child_process" fourni avec le nœud.

Ensuite, vous pouvez faire quelque chose comme:

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

Ensuite, tout ce que vous avez à faire est de vous assurer que vous êtes import sysdans votre script python, puis vous pouvez accéder à l' arg1aide de sys.argv[1], en arg2utilisant sys.argv[2], etc.

Pour renvoyer des données au nœud, procédez comme suit dans le script python:

print(dataToSendBack)
sys.stdout.flush()

Et puis le nœud peut écouter les données en utilisant:

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

Étant donné que cela permet de passer plusieurs arguments à un script à l'aide de spawn, vous pouvez restructurer un script python de sorte que l'un des arguments décide de la fonction à appeler et que l'autre argument soit transmis à cette fonction, etc.

J'espère que c'était clair. Faites-moi savoir si quelque chose doit être clarifié.

NeverForgetY2K
la source
17
@ PauloS.Abreu: Le problème que j'ai avec, execc'est qu'il renvoie un tampon au lieu d'un flux, et si vos données dépassent le maxBufferparamètre, qui par défaut est de 200 Ko, vous obtenez une exception de dépassement de tampon et votre processus est tué. Étant donné qu'il spawnutilise des flux, il est plus flexible que exec.
NeverForgetY2K
2
Juste une petite note, si vous utilisez un nœud, vous ne devriez probablement pas utiliser le mot clé process
alexvicegrab
2
Comment dois-je installer les dépendances pip externes? J'ai besoin de numpy pour un projet et je ne peux pas le faire fonctionner car il ne l'a pas installé.
javiergarval
2
@javiergarval Ce serait mieux adapté comme une nouvelle question au lieu d'un commentaire.
NeverForgetY2K
3
existe-t-il un autre moyen de renvoyer des données à partir de python que par impression? Mon script python génère beaucoup de données de journal et, apparemment, il a du mal à vider toutes ces données
lxknvlk
112

Exemple pour les personnes qui ont des antécédents en Python et qui souhaitent intégrer leur modèle d'apprentissage automatique dans l'application Node.js:

Il utilise le child_processmodule principal:

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

Il ne nécessite pas de sysmodule dans votre script Python.

Vous trouverez ci-dessous une manière plus modulaire d'effectuer la tâche en utilisant 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
la source
8
Je suis surpris que cela n'ait pas obtenu plus de votes. Bien que la réponse de @ NeverForgetY2K soit correcte, cette réponse contient un exemple plus détaillé, y compris l'écoute de port, et utilise joliment les conventions JS les plus modernes comme const & promises.
Mike Williamson
2
Excellent exemple. La promesse était bonne pour détecter certaines erreurs que j'avais sur le script python.
htafoya
38

Le python-shellmodule by extrabaconest un moyen simple d'exécuter des scripts Python à partir de Node.js avec une communication interprocessus basique mais efficace et une meilleure gestion des erreurs.

Installation: npm install python-shell .

Exécution d'un script Python simple:

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

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

Exécuter un script Python avec des arguments et des options:

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

Pour la documentation complète et le code source, consultez https://github.com/extrabacon/python-shell

Soumik Rakshit
la source
3
Ce problème m'empêche de l'utiliser - github.com/extrabacon/python-shell/issues/179
mhlavacka
1
Si vous obtenez cette erreur - TypeError: PythonShell.run n'est pas une fonction Alors assurez-vous de l'importer comme ceci var {PythonShell} = require ('python-shell');
Mohammed
4

Vous pouvez maintenant utiliser des bibliothèques RPC qui prennent en charge Python et Javascript comme zerorpc

De leur première page:

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

Serveur 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
la source
Vous pouvez également utiliser socket.io à la fois sur le nœud et le côté Python.
Bruno Gabuzomeu
3

La plupart des réponses précédentes appellent le succès de la promesse dans le ("données"), ce n'est pas la bonne façon de le faire parce que si vous recevez beaucoup de données, vous n'obtiendrez que la première partie. Au lieu de cela, vous devez le faire sur l'événement de fin.

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;

Appel:

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

python:

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

Je suis sur le nœud 10 et le processus enfant 1.0.2. Les données de python sont un tableau d'octets et doivent être converties. Juste un autre exemple rapide de faire une requête http en python.

nœud

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 pas un exemple artificiel puisque le module http du nœud ne charge pas quelques requêtes que je dois faire

1mike12
la source
J'ai un serveur principal construit sur nodejs et j'ai peu de scripts python liés à l'apprentissage automatique que je génère en utilisant le processus enfant par le biais de nodejs chaque fois que je reçois la demande sur mon serveur nodejs. Comme suggéré dans ce fil. Ma question est, est-ce la bonne façon de le faire ou je peux faire exécuter mon script python comme un service flask lié à un port en utilisant zmq et exécuter une promesse de nodejs à ce service. En fait, ce que je veux dire, dans quel sens est la sauvegarde de la mémoire et la méthode d'économie de vitesse?
Aswin
1
Vous voulez probablement que les trucs python fonctionnent indépendamment. Vous ne voulez pas de dépendances de codes durs, en particulier pour quelque chose de plus compliqué comme un service ml. Et si vous vouliez ajouter une autre pièce à cette architecture? Comme une couche de mise en cache devant le ml, ou un moyen d'ajouter des paramètres supplémentaires au modèle ml? C'est aussi de la mémoire pour exécuter un serveur python, mais vous aurez probablement besoin de flexibilité. Plus tard, vous pouvez séparer les deux pièces sur deux serveurs
1mike12
Il a demandé s'il pouvait appeler une fonction , cela ne répond pas à la question.
K - La toxicité du SO augmente.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" haha ​​love it mate!
K - La toxicité du SO augmente.
0

Vous pouvez prendre votre python, le transpiler , puis l'appeler comme s'il s'agissait de javascript. J'ai fait cela avec succès pour les screeps et je l'ai même fait fonctionner dans le navigateur à la brython .

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

Cela a fonctionné pour moi. Votre python.exe doit être ajouté à vos variables de chemin pour cet extrait de code. Assurez-vous également que votre script python se trouve dans votre dossier de projet.

nas_levente
la source