PyJa — calling Java from Python part 1

As mentioned in the previous article here is the second part of the story: calling Java from Python.

Calling Java from Python

As I mentioned there exists a way in the other direction too: you can access Java classes and methods from your Python code. Now let me describe some methods. In this section I won’t call any Dropbox API either. Besides this, as I always mention, the Python API has a bit more functionality than the Java one.

Py4J

As the first candidate I’ll take a look at Py4J.

As the documentation mentions, Py4J can access Java objects in a JVM. So you have to run your Java application parallel to the Python script with Py4J. This means that you cannot write an application I did in the previous article as a sample: add up some numbers provided as program arguments.

But the tutorial at the homepage is very good. It shows you how to create Java classes (applications to be correct) which are accessible from the Python code — with Py4J. And the example application will be the same as previously — adapted to the requirements of Py4j.

Installation

To get Py4J running you need to install it as a Python module and get the JAR to your Java application’s class path to use the required classes. Py4J installs to Python very easily (if you have pip installed):

pip install py4j

And for the Java part: create a maven project and add the dependency or with a simple Java application download the JAR and add it to your class path. As for me I’ll take the simple Java application approach and get the 0.8.1 version of the application.

Example

The example application is simple: first I show you the Java code, then the Python which calls the Java.

public class Py4JExample {

    public static void main(String[] args) {
        GatewayServer gatewayServer = new GatewayServer(new Py4JExample());
        gatewayServer.start();
    }

    public int addNumbers(int... numbers) {
        int sum = 0;
        for(int i : numbers) {
            sum += i;
        }
        return sum;
    }
}

The method “addNumbers” speaks for itself: it adds up the provided int parameters and returns the value of the sum. The highlighted lines represent the Py4J binding: the first line creates a GatewayServer for the class to be available for the Python script and the second line starts it.

from py4j.java_gateway import JavaGateway
import sys

gateway = JavaGateway()
app = gateway.entry_point

nums = gateway.new_array(gateway.jvm.int, len(sys.argv))
for i in range(1, len(sys.argv)):
    nums[i-1] = int(sys.argv[i])

print("The result is: %d"%app.addNumbers(nums))

The script creates a JavaGateway object and get its entry point (the class we added to the GatewayServer’s constructor in the Java example).

The arguments provided to the Python script have to be converted into a Java array because varargs cannot be used as a method parameter when calling Java from Python. In Py4J you have some converters to use Python lists, maps and sets in Java. As one add-on to my previous article, Py4J has a feature to implement Java interfaces and allot them to be called from Java (as a callback).

What if you want to use Py4J to call the Java Dropbox API? You have to implement a class which creates a GatewayServer with the one class which has the methods you want to call. If you want to call multiple classes, you have to create more classes which run separate GatewayServers and pass the required parameters between them or you write a class which exposes all relevant methods from the different objects and add this class as an access point.

So this solution isn’t easy too. Besides this you can reach with the started endpoint the core Java features available in every JVM: so java.utils, java.io, java.math and so on, the core libraries which do not require any dependency as we think about it as a maven project.

The release cycles at Py4J are not constant, between 0.7 and 0.8 there have been almost two years, between 0.8 and 0.8.1 almost 1 year. The developer is keen on making the library better as he encourages issues and replies to them.

JPype

JPype is an old tool, the last change has been in 2011. So I would say this project is dead — on the original SourceForge repository and the official web-site. However there is a GitHub project which continues the development of JPype. The last changes in the sources were recently. This means that JPype has been resurrected.

Installation

For windows there are some exe installers available. For my Mac I downloaded the zip’d installer and it did not work. The command

python setup.py install

yielded an exception: the include file “jni.h” was not found. So I looked up where is this file located on my Mac and I found that I have to add

/System/Library/Frameworks/JavaVM.framework/Headers/

to the include folders of the setup script. It needs a bit tinkering. And if we saw the requirement of jni.h which I have to explain I guess: JPype is C++ based, and it gives a native Java experience: it connects to the JVM natively. So in this case you do not have to launch a server, which exposes all the needed methods of one single class.

Example

The examples at the GitHub page are older too (last change 2 years ago) but I think they should work.

In this article I’ll stick to my example: call a Java class which sums up an array of numbers provided as the arguments of the Python script. If you want to use a Java class, you have to compile it first. After this you can call it — you have to add it to the JVM starting method as a class path entry.

You can add JAR files to the class path too at starting the JVM so if you have a library which you want to use you can do it. The main part is to configure the class path accordingly.

In this example I’m going to call this Java class (both of the methods):

public class JPypeExample {

 public static int addition(Integer... numbers) {
 int sum = 0;
 for(int i : numbers) {
 sum += i;
 }
 return sum;
 }

 public int addNumbers(int a, int b, int c, int d) {
 return a+b+c+d;
 }
}

A sample Python script:

import jpype
import os

jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=%s"%os.path.abspath("."))

JPypeExample= jpype.JClass("JPypeExample")

numbers = jpype.JArray(jpype.java.lang.Integer, 1)(4)
numbers[0] = jpype.java.lang.Integer(13)
numbers[1] = jpype.java.lang.Integer(1)
numbers[2] = jpype.java.lang.Integer(20)
numbers[3] = jpype.java.lang.Integer(8)

jPypeExample = JPypeExample()

print "The result is: %d"%jPypeExample.addNumbers(13,1,20,8)

print "The result is: %d"%JPypeExample.addition(numbers)
jpype.shutdownJVM()

This shows that you should use the full path to your compiled Java class (in my example these two are in the same folder).

The array is needed to fulfill the var-args contract in the Java class. I tried to figure out how to use a primitive int array but it will not work out.


 

OK. I tried. I got JPype compiled on my System. I created a super Java class to demonstrate the functionality. I wired Python up. But helpless. I get an exception of:

Traceback (most recent call last):
 File "jpype_example.py", line 6, in <module>
  example = JClass("JPypeExample") 
 File "/Library/Python/2.7/site-packages/jpype/_jclass.py", line 54, in JClass
  raise _RUNTIMEEXCEPTION.PYEXC("Class %s not found" % name) jpype._jexception.ExceptionPyRaisable: java.lang.Exception: Class JPypeExample not found

I googled after a solution. I found one interesting StackOverflow issue which has been solved but it did not help. There was the different Python and Java Compiler bit-version the cause. On my Mac there is no such problem: I’ve the 64 bit of both.

After a bit more searching I found that JPype is hard coded to the Java 6 version. This is bad because I have a Java 7 compiler as default. Nevertheless I compiled the Java class with

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/javac JPypeExample.java

and this works as a miracle. My python script is executing and everything is fine. I’ve added the compiled class to the repository too to avoid problems.

Next time

This is the end of this article. It covers two libraries for Java access from Python. The next time I’ll take Jython and Pyjnius and make my examples with them.

Advertisements

3 thoughts on “PyJa — calling Java from Python part 1

  1. Pingback: PyJa — calling Java from Python part 2 | HaHaMo Group
  2. Pretty component to content. I just stumbled upon your web site and in accession capital to claim that I acquire in fact enjoyed account your blog posts.
    Anyway I’ll be subscribing in your augment or even I success you access consistently quickly.

  3. Fantastic goods from you, man. I have understand
    your stuff previous to and you are just too magnificent.
    I actually like what you’ve acquired here, certainly like what you are stating and the way
    in which you say it. You make it enjoyable and you still care for to keep it
    wise. I cant wait to read much more from you. This is actually a great website.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s