118

I'd like to call a Python function from JavaScript code, because there isn't an alternative in JavaScript for doing what I want. Is this possible? Could you adjust the below snippet to work?

JavaScript code:

var tag = document.getElementsByTagName("p")[0];
text = tag.innerHTML;
// Here I would like to call the Python interpreter with Python function
arrOfStrings = openSomehowPythonInterpreter("~/pythoncode.py", "processParagraph(text)");

~/pythoncode.py contains functions using advanced libraries that don't have an easy to write equivalent in JavaScript:

import nltk # is not in JavaScript
def processParagraph(text):
  ...
  nltk calls
  ...
  return lst # returns a list of strings (will be converted to JavaScript array)
Gaurang Tandon
  • 6,504
  • 11
  • 47
  • 84
xralf
  • 3,312
  • 45
  • 129
  • 200
  • 14
    No, browsers (fortunately) won't execute arbitrary Python code. You'll want to run that in a server. – Fred Foo Nov 01 '12 at 10:54
  • 2
    Javascript runs on the client. I assume the python runs on the server. You could send an ajax request to the server. It won't be fast. – John Dvorak Nov 01 '12 at 10:55
  • 2
    Using ajax, send text to a python script on your server. Set up the script to return data in an easy to parse (for js) notation (like JSON) and assign the result to arrOfStrings in the success handler. – Asad Saeeduddin Nov 01 '12 at 10:56
  • Also, finding a python engine for javascript won't be easy either – John Dvorak Nov 01 '12 at 10:57
  • 9
    You can run the official Python interpreter in the browser by compiling it using clang and [Emscripten](https://github.com/kripken/emscripten). This has been done before. –  Nov 01 '12 at 11:02
  • You can run IronPython (kind of Python.Net) in the browser with silverlight, but I don't know if NLTK is available for IronPython. – Paulo Scardine Nov 01 '12 at 11:18
  • 2
    @FredFoo, What would actually be fortunate is if browsers _didn't_ run ECMAScript (which is called JavaScript for rather dubious historical reasons.) What would also be fortunate is if browsers had been running a secure subset (which is what anyone means by running anything in a browser, your straw man notwithstanding) of Python since the '90s so we wouldn't have to be dealing with the current web mess. – SO_fix_the_vote_sorting_bug Apr 03 '20 at 15:26
  • **For those finding this on Google**, checkout the modern answer here : https://stackoverflow.com/a/68497604/11173996 – Extreme Jul 23 '21 at 10:20

7 Answers7

72

All you need is to make an ajax request to your pythoncode. You can do this with jquery http://api.jquery.com/jQuery.ajax/, or use just javascript

$.ajax({
  type: "POST",
  url: "~/pythoncode.py",
  data: { param: text}
}).done(function( o ) {
   // do something
});
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
  • 1
    It looks interesting. Where could be the call of `processParagraph(text)` so that the return values ends in the variable `arrOfStrings`? – xralf Nov 01 '12 at 11:45
  • 2
    I'm running [this](http://pastebin.com/4iM2C0nD) code in firebug, but it logs `[]` – xralf Nov 01 '12 at 12:00
  • 2
    OK, so how is it right? My Python file contains the correct function. Should I call the function in Python and the argument will be sys.argv[1]? – xralf Nov 01 '12 at 12:25
  • 1
    Could you elaborate? When calling a python function via ajax post, does it call the __ main __ method? Because main cannot return a string. Can we expect to get any return values back? Does the data attribute correspond to the **kwargs dictionary in python, so we must have a method that accepts that? Otherwise, how do we specify the function in the python module? – NuclearPeon Apr 22 '13 at 20:59
  • 9
    Thanks for the answer, but in order for the python script to execute it has to be deployed by a web server that supports it via CGI or WSGI. Could you please include in your answer how to solve that problem? – Matteo Nov 17 '14 at 00:07
  • 4
    Oh I'd be very happy to edit your answer if I knew how to do that, I was hoping you could provide some advice, cause I'm getting this error `XMLHttpRequest cannot load file:~/pythoncode.py. Cross origin requests are only supported for protocol schemes: http, data, chrome-extension, https, chrome-extension-resource` and even though I understood what the problem is don't know how to solve it. Any useful pointer? Thanks a lot. (btw...chessheaven seems really awesome! I'll try it out for sure, good thing you put a cute girl in your profile picture ;)) – Matteo Nov 17 '14 at 00:18
  • @NuclearPeon I suspect it's actually Python CGI, so it would print, not return. :D – wallabra Jan 13 '21 at 07:48
32

From the document.getElementsByTagName I guess you are running the javascript in a browser.

The traditional way to expose functionality to javascript running in the browser is calling a remote URL using AJAX. The X in AJAX is for XML, but nowadays everybody uses JSON instead of XML.

For example, using jQuery you can do something like:

$.getJSON('http://example.com/your/webservice?param1=x&param2=y', 
    function(data, textStatus, jqXHR) {
        alert(data);
    }
)

You will need to implement a python webservice on the server side. For simple webservices I like to use Flask.

A typical implementation looks like:

@app.route("/your/webservice")
def my_webservice():
    return jsonify(result=some_function(**request.args)) 

You can run IronPython (kind of Python.Net) in the browser with silverlight, but I don't know if NLTK is available for IronPython.

Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
16

Communicating through processes

Example:

Python: This python code block should return random temperatures.

# sensor.py

import random, time
while True:
    time.sleep(random.random() * 5)  # wait 0 to 5 seconds
    temperature = (random.random() * 20) - 5  # -5 to 15
    print(temperature, flush=True, end='')

Javascript (Nodejs): Here we will need to spawn a new child process to run our python code and then get the printed output.

// temperature-listener.js

const { spawn } = require('child_process');
const temperatures = []; // Store readings

const sensor = spawn('python', ['sensor.py']);
sensor.stdout.on('data', function(data) {

    // convert Buffer object to Float
    temperatures.push(parseFloat(data));
    console.log(temperatures);
});
Michael Obasi
  • 171
  • 1
  • 6
12

Typically you would accomplish this using an ajax request that looks like

var xhr = new XMLHttpRequest();
xhr.open("GET", "pythoncode.py?text=" + text, true);
xhr.responseType = "JSON";
xhr.onload = function(e) {
  var arrOfStrings = JSON.parse(xhr.response);
}
xhr.send();
Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
5

You cannot run .py files from JavaScript without the Python program like you cannot open .txt files without a text editor. But the whole thing becomes a breath with a help of a Web API Server (IIS in the example below).

  1. Install python and create a sample file test.py

    import sys
    # print sys.argv[0] prints test.py
    # print sys.argv[1] prints your_var_1
    
    def hello():
        print "Hi" + " " + sys.argv[1]
    
    if __name__ == "__main__":
        hello()
    
  2. Create a method in your Web API Server

    [HttpGet]
    public string SayHi(string id)
    {
        string fileName = HostingEnvironment.MapPath("~/Pyphon") + "\\" + "test.py";          
    
        Process p = new Process();
        p.StartInfo = new ProcessStartInfo(@"C:\Python27\python.exe", fileName + " " + id)
        {
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };
        p.Start();
    
        return p.StandardOutput.ReadToEnd();                  
    }
    
  3. And now for your JavaScript:

    function processSayingHi() {          
       var your_param = 'abc';
       $.ajax({
           url: '/api/your_controller_name/SayHi/' + your_param,
           type: 'GET',
           success: function (response) {
               console.log(response);
           },
           error: function (error) {
               console.log(error);
           }
        });
    }
    

Remember that your .py file won't run on your user's computer, but instead on the server.

Community
  • 1
  • 1
azakgaim
  • 309
  • 3
  • 6
4

Despite what some replies and comments suggest, there are a number of ways for using Python on the front-end. For your question in particular, see this reply.

AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
1

Take a look at JSPyBridge

It seems that you can use Python functions inside JS with this. They even have an NLTK example, which I have given here.

// Do npm i pythonia first!
import { python } from 'pythonia'
import fs from 'fs'
const nltk = await python('nltk')

// ** You can comment this if you already have it.
await nltk.download('book')

const monologue = fs.readFileSync('./shakesphere.txt', 'utf-8')

// First we use NLTK to tokenize, tag and "chunk" the words into a tree
const sentences = await nltk.sent_tokenize(monologue).then(v => v.valueOf())
const tokenized = await Promise.all(sentences.map(sentence => nltk.word_tokenize(sentence)))
const tagged = await Promise.all(tokenized.map(tok => nltk.pos_tag(tok)))
const chunked = await nltk.ne_chunk_sents$(tagged, { binary: true })

// Some tree traversal logic to extract all the Named Entities (NE)
async function extractEntityNames (t) {
  const entityNames = []
  if (await t.label$) {
    const label = await t.label()
    if (label === 'NE') {
      for (const child of await t.valueOf()) {
        entityNames.push(child[0])
      }
    } else {
      for await (const child of t) {
        entityNames.push(...await extractEntityNames(child))
      }
    }
  }
  return entityNames
}

const entityNames = []

// Run the function above on each of the chunked trees
for await (const tree of chunked) {
  entityNames.push(...await extractEntityNames(tree))
}

// Compile the frequencies of each word
const frequencies = entityNames.reduce((acc, curr) => (acc[curr] ??= 0, acc[curr]++, acc), {})
// Turn it to an array and list by most common
const result = Object.entries(frequencies).map(([k, v]) => [k, v]).sort((a, b) => b[1] - a[1])
// Log it out, you should get [ [ 'Romeo', 5 ], [ 'Juliet', 2 ], [ 'Deny', 1 ], [ 'Montague', 1 ], ... ]
console.log(result)
// Exit python
python.exit()
Akul Goel
  • 11
  • 2