So rufen Sie eine Python-Funktion aus Node.js auf

206

Ich habe eine Express Node.js-Anwendung, aber ich habe auch einen Algorithmus für maschinelles Lernen, der in Python verwendet werden kann. Gibt es eine Möglichkeit, Python-Funktionen aus meiner Node.js-Anwendung aufzurufen, um die Leistungsfähigkeit von Bibliotheken für maschinelles Lernen zu nutzen?

Genjuro
quelle
4
Node-Python . Ich habe es aber nie selbst benutzt.
Universität
21
Zwei Jahre später node-pythonscheint es sich um ein aufgegebenes Projekt zu handeln.
Imrek
Siehe auch github.com/QQuick/Transcrypt, um Python in Javascript zu kompilieren und dann aufzurufen
Jonathan

Antworten:

260

Der einfachste Weg, den ich kenne, ist die Verwendung des Pakets "child_process", das im Lieferumfang des Knotens enthalten ist.

Dann können Sie so etwas tun wie:

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

Dann müssen Sie nur noch sicherstellen, dass Sie import sysin Ihrem Python-Skript sind, und dann können Sie mit arg1using sys.argv[1], arg2using sys.argv[2]usw. darauf zugreifen .

Um Daten an den Knoten zurückzusenden, gehen Sie im Python-Skript wie folgt vor:

print(dataToSendBack)
sys.stdout.flush()

Und dann kann der Knoten auf Daten warten, indem er:

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

Da auf diese Weise mehrere Argumente mithilfe von Spawn an ein Skript übergeben werden können, können Sie ein Python-Skript so umstrukturieren, dass eines der Argumente entscheidet, welche Funktion aufgerufen werden soll, und das andere Argument an diese Funktion usw. übergeben wird.

Hoffe das war klar. Lassen Sie mich wissen, wenn etwas geklärt werden muss.

NeverForgetY2K
quelle
16
@ PauloS.Abreu: Das Problem, mit dem ich habe, execist, dass es einen Puffer anstelle eines Streams zurückgibt. Wenn Ihre Daten die maxBufferEinstellung überschreiten , die standardmäßig 200 KB beträgt, erhalten Sie eine Pufferüberschreitungsausnahme und Ihr Prozess wird beendet. Da spawnStreams verwendet werden, ist es flexibler als exec.
NeverForgetY2K
2
Nur eine kleine Anmerkung, wenn Sie Knoten verwenden, sollten Sie wahrscheinlich nicht das Prozessschlüsselwort verwenden
alexvicegrab
2
Wie soll ich externe Pip-Abhängigkeiten installieren? Ich brauche Numpy für ein Projekt und kann es nicht ausführen, weil es nicht installiert ist.
Javiergarval
2
@javiergarval Das wäre besser als neue Frage als als Kommentar geeignet.
NeverForgetY2K
3
Gibt es eine andere Möglichkeit, Daten aus Python zurückzugeben, als durch Drucken? Mein Python-Skript gibt viele Protokolldaten aus und anscheinend hat es Probleme, all diese Daten zu
löschen
111

Beispiel für Personen mit Python-Hintergrund, die ihr Modell für maschinelles Lernen in die Anwendung Node.js integrieren möchten:

Es verwendet das child_processKernmodul:

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

Es ist kein sysModul in Ihrem Python-Skript erforderlich .

Im Folgenden finden Sie eine modularere Methode zum Ausführen der Aufgabe mit 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
quelle
8
Ich bin überrascht, dass dies nicht mehr Stimmen bekommen hat. Während die Antwort von @ NeverForgetY2K in Ordnung ist, enthält diese Antwort ein detaillierteres Beispiel, einschließlich des Abhörens von Ports, und verwendet die moderneren JS-Konventionen wie const & versprechen.
Mike Williamson
2
Tolles Beispiel. Versprich mir, dass es gut war, einige Fehler zu erkennen, die ich im Python-Skript hatte.
Htafoya
38

Das python-shellModul von extrabaconist eine einfache Möglichkeit, Python-Skripte von Node.js mit einfacher, aber effizienter Kommunikation zwischen Prozessen und besserer Fehlerbehandlung auszuführen.

Installation : npm install python-shell .

Ausführen eines einfachen Python-Skripts:

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

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

Ausführen eines Python-Skripts mit Argumenten und Optionen:

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

Die vollständige Dokumentation und den Quellcode finden Sie unter https://github.com/extrabacon/python-shell

Soumik Rakshit
quelle
3
Dieses Problem
hindert
1
Wenn Sie diesen Fehler erhalten - TypeError: PythonShell.run ist keine Funktion. Stellen Sie dann sicher, dass Sie ihn wie folgt importieren. Var {PythonShell} = require ('python-shell');
Mohammed
4

Sie können jetzt RPC-Bibliotheken verwenden, die Python und Javascript unterstützen, z. B. zerorpc

Von ihrer Titelseite:

Node.js Client

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()
Geordie
quelle
Sie können socket.io auch sowohl auf der Node- als auch auf der Python-Seite verwenden.
Bruno Gabuzomeu
3

Die meisten früheren Antworten nennen den Erfolg des Versprechens im Ein ("Daten"), es ist nicht der richtige Weg, dies zu tun, denn wenn Sie viele Daten erhalten, erhalten Sie nur den ersten Teil. Stattdessen müssen Sie dies beim Endereignis tun.

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;

Anruf:

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
quelle
2

Ich bin auf Knoten 10 und untergeordneter Prozess 1.0.2. Die Daten von Python sind ein Byte-Array und müssen konvertiert werden. Nur ein weiteres kurzes Beispiel für eine http-Anfrage in Python.

Knoten

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 ist kein erfundenes Beispiel, da das http-Modul des Knotens nicht einige Anforderungen lädt, die ich stellen muss

1mike12
quelle
Ich habe ein Server-Backend, das auf NodeJS aufgebaut ist, und ich habe nur wenige Python-Skripte, die sich auf maschinelles Lernen beziehen und die ich mit dem untergeordneten Prozess-Spawn über NodeJS erhalte, wenn ich die Anfrage auf meinem NodeJS-Server erhalte. Wie in diesem Thread vorgeschlagen. Meine Frage ist, ob dies der richtige Weg ist oder ob ich mein Python-Skript wie einen Flask-Dienst ausführen lassen kann, der mit zmq an einen Port gebunden ist, und ein Versprechen von nodejs an diesen Dienst ausführen kann. Mit Recht meine ich, auf welche Weise werden Speicher und Geschwindigkeit gespart?
Aswin
1
Sie möchten wahrscheinlich, dass das Python-Zeug unabhängig läuft. Sie möchten keine Abhängigkeiten von harten Codes, insbesondere für etwas, das als ml-Dienst komplizierter ist. Was wäre, wenn Sie dieser Architektur ein weiteres Stück hinzufügen möchten? Wie eine Caching-Ebene vor dem ml oder eine Möglichkeit, dem ml-Modell zusätzliche Parameter hinzuzufügen? Es ist Speicher, um auch einen Python-Server auszuführen, aber Sie werden wahrscheinlich die Flexibilität benötigen. Später können Sie die beiden Teile auf zwei Server
aufteilen
Er fragte, ob er eine Funktion aufrufen könne , dies beantworte die Frage nicht.
K - Die Toxizität in SO nimmt zu.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" haha ​​liebe es Kumpel!
K - Die Toxizität in SO nimmt zu.
0

Sie könnten Ihre Python nehmen, transpilieren und dann so aufrufen, als wäre es Javascript. Ich habe dies erfolgreich für Screeps gemacht und es sogar im Browser a la Brython laufen lassen .

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

Das hat bei mir funktioniert. Ihre python.exe muss Ihnen Pfadvariablen für dieses Code-Snippet hinzugefügt werden. Stellen Sie außerdem sicher, dass sich Ihr Python-Skript in Ihrem Projektordner befindet.

nas_levente
quelle