Jak wywołać funkcję Python z Node.js

208

Mam aplikację Express Node.js, ale mam również algorytm uczenia maszynowego do użycia w języku Python. Czy istnieje sposób, w jaki mogę wywoływać funkcje Python z mojej aplikacji Node.js, aby korzystać z możliwości bibliotek uczenia maszynowego?

Genjuro
źródło
4
węzeł-python . Jednak nigdy go nie użyłem.
univerio
22
Dwa lata później node-pythonwydaje się być porzuconym projektem.
imrek
Zobacz także github.com/QQuick/Transcrypt, aby skompilować Pythona w javascript, a następnie wywołać go
Jonathan

Odpowiedzi:

262

Najłatwiejszy sposób, jaki znam, to użycie pakietu „child_process” dostarczanego z węzłem.

Następnie możesz zrobić coś takiego:

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

Następnie wszystko, co musisz zrobić, to upewnić się, że masz import sysw swoim skrypcie python, a następnie możesz uzyskać dostęp arg1za pomocą sys.argv[1], arg2za pomocą sys.argv[2]i tak dalej.

Aby wysłać dane z powrotem do węzła, wykonaj następujące czynności w skrypcie python:

print(dataToSendBack)
sys.stdout.flush()

A następnie węzeł może nasłuchiwać danych za pomocą:

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

Ponieważ pozwala to na przekazanie wielu argumentów do skryptu za pomocą spawn, możesz zrestrukturyzować skrypt Pythona, aby jeden z argumentów decydował, którą funkcję wywołać, a drugi argument zostanie przekazany do tej funkcji itp.

Mam nadzieję, że to było jasne. Daj mi znać, jeśli coś wymaga wyjaśnienia.

NeverForgetY2K
źródło
17
@ PauloS.Abreu: Mam problem z exectym, że zwraca bufor zamiast strumienia, a jeśli twoje dane przekraczają maxBufferustawienie, które domyślnie wynoszą 200kB, otrzymujesz wyjątek przekroczony przez bufor i twój proces zostaje zabity. Ponieważ spawnużywa strumieni, jest bardziej elastyczny niż exec.
NeverForgetY2K
2
Tylko mała uwaga, jeśli używasz węzła, prawdopodobnie nie powinieneś używać słowa kluczowego procesu
alexvicegrab
2
Jak zainstalować zewnętrzne zależności pip? Potrzebuję numpy do projektu i nie mogę go uruchomić, ponieważ nie ma go zainstalowanego.
javiergarval
2
@javiergarval To byłoby lepsze jako nowe pytanie zamiast komentarza.
NeverForgetY2K
3
czy istnieje inny sposób zwracania danych z Pythona niż drukowanie? Mój skrypt Pythona wyświetla wiele danych dziennika i najwyraźniej ma problem z
opróżnieniem
111

Przykład dla osób wywodzących się z języka Python i chcących zintegrować swój model uczenia maszynowego z aplikacją Node.js:

Wykorzystuje child_processmoduł podstawowy:

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

Nie wymaga sysmodułu w skrypcie Python.

Poniżej znajduje się bardziej modułowy sposób wykonywania zadania przy użyciu 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
źródło
8
Dziwię się, że nie ma więcej głosów. Chociaż odpowiedź @ NeverForgetY2K jest w porządku, ta odpowiedź zawiera bardziej szczegółowy przykład, w tym nasłuchiwanie portu, i ładnie wykorzystuje bardziej nowoczesne konwencje JS, takie jak const i obiecuje.
Mike Williamson,
2
Świetny przykład. Obiecuję, że dobrze było wykryć niektóre błędy, które miałem w skrypcie Pythona.
htafoya
38

python-shellModułemextrabacon to prosty sposób na uruchamianie skryptów Pythona z node.js z basic, ale sprawna komunikacja między procesami i lepsze obsługa błędów.

Instalacja: npm install python-shell .

Uruchamianie prostego skryptu Python:

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

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

Uruchamianie skryptu Python z argumentami i opcjami:

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

Aby uzyskać pełną dokumentację i kod źródłowy, sprawdź https://github.com/extrabacon/python-shell

Soumik Rakshit
źródło
3
Ten problem powstrzymuje mnie od korzystania z niego - github.com/extrabacon/python-shell/issues/179
mhlavacka 20.02.19
1
Jeśli pojawia się ten błąd - TypeError: PythonShell.run nie jest funkcją Następnie upewnij się, że importujesz go w ten sposób var {PythonShell} = wymagany ('python-shell');
Mohammed,
4

Możesz teraz używać bibliotek RPC, które obsługują Python i JavaScript, takich jak zerorpc

Z ich pierwszej strony:

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

Serwer 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
źródło
Możesz także używać socket.io zarówno po stronie Węzła, jak i Pythona.
Bruno Gabuzomeu
3

Większość wcześniejszych odpowiedzi nazywa powodzenie obietnicy włączonej („dane”), nie jest to właściwy sposób, aby to zrobić, ponieważ jeśli otrzymasz dużo danych, dostaniesz tylko pierwszą część. Zamiast tego musisz to zrobić na zdarzeniu końcowym.

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;

Połączenie:

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

pyton:

try:
    argu = json.loads(sys.argv[1])
except:
    raise Exception("error while loading argument")
bormat
źródło
2

Jestem na węźle 10 i proces potomny 1.0.2. Dane z Pythona są tablicą bajtów i muszą zostać przekonwertowane. Kolejny szybki przykład wysyłania żądania http w pythonie.

węzeł

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 nie jest wymyślonym przykładem, ponieważ moduł http węzła nie ładuje kilku żądań, które muszę wykonać

1mike12
źródło
Mam wbudowaną backend serwera na nodejs i mam niewiele skryptów pythonowych związanych z uczeniem maszynowym, które odradzam przy użyciu procesu potomnego odradzania za pośrednictwem nodejs za każdym razem, gdy otrzymuję żądanie na moim serwerze nodejs. Jak sugerowano w tym wątku. Moje pytanie brzmi: czy jest to właściwy sposób, czy mogę uruchomić skrypt Pythona jak usługa kolby powiązana z portem za pomocą zmq i uruchomić obietnicę od nodejs do tej usługi? Mam na myśli to, w jaki sposób oszczędzanie pamięci i metoda oszczędzania prędkości?
Aswin,
1
Prawdopodobnie chcesz, aby Python działał niezależnie. Nie chcesz zależności od twardego kodu, szczególnie w przypadku czegoś bardziej skomplikowanego, jak usługa ml. Co jeśli chcesz dodać kolejny element do tej architektury? Jak warstwa buforująca przed ml, czy sposób na dodanie dodatkowych parametrów do modelu ml? Pamięć działa również na serwerze Python, ale prawdopodobnie będziesz potrzebować elastyczności. Później możesz podzielić te dwa elementy na dwa serwery
1mike12
Zapytał, czy mógłby wywołać funkcję , to nie odpowiada na pytanie.
K - Toksyczność w SO rośnie.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" haha ​​love it mate!
K - Toksyczność w SO rośnie.
0

Możesz wziąć swój python, przetransponować go, a następnie wywołać tak, jakby to był javascript. Zrobiłem to z powodzeniem dla piargów, a nawet uruchomiłem w przeglądarce a la brython .

Jonathan
źródło
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);
}); 

To zadziałało dla mnie. Twój python.exe musi zostać dodany do zmiennych ścieżek dla tego fragmentu kodu. Upewnij się także, że skrypt Pythona znajduje się w folderze projektu.

nas_levente
źródło