Como chamar uma função Python do Node.js

209

Eu tenho um aplicativo Express Node.js, mas também tenho um algoritmo de aprendizado de máquina para usar no Python. Existe uma maneira de chamar funções do Python do meu aplicativo Node.js. para usar o poder das bibliotecas de aprendizado de máquina?

Genjuro
fonte
4
nó-python . Nunca usei sozinho, no entanto.
Univerio
23
Dois anos depois, node-pythonparece ser um projeto abandonado.
Imrek
Veja também github.com/QQuick/Transcrypt para compilar python em javascript e, em seguida, invocá-lo
Jonathan

Respostas:

262

A maneira mais fácil que eu conheço é usar o pacote "child_process" que é fornecido com o nó.

Então você pode fazer algo como:

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

Então, tudo o que você precisa fazer é ter certeza de que está import sysno seu script python e poder acessar arg1usando sys.argv[1], arg2usando sys.argv[2]e assim por diante.

Para enviar dados de volta ao nó, faça o seguinte no script python:

print(dataToSendBack)
sys.stdout.flush()

E o nó pode escutar dados usando:

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

Como isso permite que vários argumentos sejam passados ​​para um script usando spawn, você pode reestruturar um script python para que um dos argumentos decida qual função chamar e o outro argumento seja passado para essa função etc.

Espero que isso esteja claro. Deixe-me saber se algo precisa de esclarecimentos.

NeverForgetY2K
fonte
17
@ PauloS.Abreu: O problema que tenho execé que ele retorna um buffer em vez de um fluxo, e se seus dados excederem a maxBufferconfiguração, cujo padrão é 200kB, você obterá uma exceção excedida no buffer e seu processo será interrompido. Como spawnusa fluxos, é mais flexível que exec.
NeverForgetY2K
2
Apenas uma pequena nota, se você usar nó que você provavelmente não deve usar a palavra-chave processo
alexvicegrab
2
Como devo instalar dependências externas de pip? Preciso de numpy para um projeto e não posso fazê-lo funcionar porque não o tem instalado.
Javiergarval 02/01/19
2
@javiergarval Isso seria mais adequado como uma nova pergunta em vez de um comentário.
NeverForgetY2K
3
existe alguma outra maneira de retornar dados do python que não seja imprimindo? Meu script python gera um monte de dados de log e, aparentemente, ele tem problemas rubor todos esses dados
lxknvlk
112

Exemplo para pessoas com experiência em Python e que desejam integrar seu modelo de aprendizado de máquina no aplicativo Node.js.

Ele usa o child_processmódulo 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!'))

Não requer sysmódulo no seu script Python.

Abaixo está uma maneira mais modular de executar a tarefa 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
fonte
8
Estou surpreso que isso não tenha conseguido mais votos. Enquanto a resposta do @ NeverForgetY2K é boa, esta resposta contém um exemplo mais detalhado, incluindo a escuta da porta, e usa bem as convenções mais modernas de JS, como const & promises.
26818 Mike Williamson
2
Ótimo exemplo. Prometo que foi bom detectar alguns erros que tive no script python.
Htafoya # 1
38

O python-shellmódulo by extrabaconé uma maneira simples de executar scripts Python a partir do Node.js com comunicação básica, mas eficiente entre processos e melhor tratamento de erros.

Instalação: npm install python-shell .

Executando um script Python simples:

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

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

Executando um script Python com argumentos e opções:

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 obter a documentação completa e o código-fonte, consulte https://github.com/extrabacon/python-shell

Soumik Rakshit
fonte
3
Esta questão está me impedindo de usá-lo - github.com/extrabacon/python-shell/issues/179
mhlavacka
1
Se você estiver recebendo esse erro - TypeError: PythonShell.run não é uma função. Em seguida, certifique-se de importá-lo desta forma var {PythonShell} = require ('python-shell');
Mohammed
4

Agora você pode usar bibliotecas RPC compatíveis com Python e Javascript, como zerorpc

Na primeira página:

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
fonte
Você também pode usar o socket.io no lado do nó e do Python.
Bruno Gabuzomeu 28/02
3

A maioria das respostas anteriores chama o sucesso da promessa no ("dados"), não é a maneira correta de fazê-lo, porque se você receber muitos dados, receberá apenas a primeira parte. Em vez disso, você deve fazer isso no evento final.

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;

Ligar:

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

Pitão:

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

Estou no nó 10 e processo filho 1.0.2. Os dados do python são uma matriz de bytes e precisam ser convertidos. Apenas outro exemplo rápido de fazer uma solicitação http em python.

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 não é um exemplo artificial, pois o módulo http do nó não carrega alguns pedidos que eu preciso fazer

1mike12
fonte
Eu tenho uma compilação de servidor back-end no nodejs e tenho poucos scripts python relacionados ao aprendizado de máquina que são gerados usando o processo filho gerado por nodejs sempre que recebo a solicitação no meu servidor nodejs. Conforme sugerido neste tópico. Minha pergunta é: essa é a maneira correta de fazer isso ou posso fazer com que meu script python seja executado como um serviço de balão vinculado a uma porta usando zmq e execute uma promessa do nodejs para este serviço. Por certo, o que quero dizer é: de que maneira a economia de memória e o método de economia de velocidade?
Aswin
1
Você provavelmente quer que o material python seja executado independentemente. Você não deseja dependências de códigos físicos, especialmente para algo mais complicado como um serviço de ml. E se você quisesse adicionar outra peça a essa arquitetura? Como uma camada de armazenamento em cache na frente do ml, ou uma maneira de adicionar parâmetros extras ao modelo ml? Também é memória executar um servidor python, mas você provavelmente precisará da flexibilidade. Mais tarde, você pode separar as duas peças para dois servidores
1mike12
Ele perguntou se poderia chamar uma função , isso não responde à pergunta.
K - A toxicidade no SO está crescendo.
2
@ 1mike12 "karl_morrison_is_a_pedant ()" haha ​​adoro companheiro!
K - A toxicidade no SO está crescendo.
0

Você pode pegar seu python, transpilar e depois chamá-lo como se fosse javascript. Eu fiz isso com sucesso para screeps e até consegui rodar no navegador à la brython .

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

Isso funcionou para mim. Seu python.exe deve ser adicionado às variáveis ​​de caminho para esse trecho de código. Além disso, verifique se o script python está na pasta do projeto.

nas_levente
fonte