Did you know? Programmers convert coffee to code.

If you like my articles, sponsor me a coffee.

PyJa — calling Java from Python part 2

Previously I’ve written about Py4J and JPype as packages to call Java from Python. In this article I’ll write about Jython and Pyjnius.

And this is the last article in this series — I hope 🙂

I’ll take a look at Jython and PyJNIus as the last two tools in the list for calling Java classes from Python.

PyJNIus

The concept is simple: enable to use Java classes in Python via JNI. As the homepage states

PyJNIus is a “Work In Progress”.

And the project moves on smoothly. There bugs are fixed and the documentation is tried to kept up-to-date.

The concept stays the same as before: I’ll make a sample app which calls some Java functionality — and show how to access an external class. And the main path for the development is using this wrapper for Android development (there is a parallel development of PyOBJus to enable access to Objective-C classes from Python).

Installation

This one is simple: just execute

sudo pip install pyjnius

on your command line and you get PyJNIus installed. And you can use the tool.

Examples

So for me I’ll leave the simple calling behind (what you can read on most of the sites which say to be a tutorial to PyJNIus) and do not write a console application where you load a Stack and add / remove elements.

But I’ll say some words about method overloading. Because I do not jump right into a new programming tool I tried the sample console application with a List instead of a Stack.

If you try to execute following commands in your Python console

>>> from jnius import autoclass
>>> List = autoclass("java.util.List")
>>> list = List()

you’ll get the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius_export_class.pxi", line 147, in jnius.JavaClass.__init__ (jnius/jnius.c:13884)
  File "jnius_export_class.pxi", line 172, in jnius.JavaClass.call_constructor (jnius/jnius.c:14173)
jnius.JavaException: No constructor available

This is because the List is an interface and cannot be instantiated. The solution is to convert it to, let’s say, an ArrayList:

>>> List = autoclass("java.util.ArrayList")
>>> list = List()
>>>

Now we’re good so add some elements to our list: with Strings it’ll go fine, however with numbers it is a problem:

>>> list.add("Hello World!")
True
>>> list.add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius_export_class.pxi", line 824, in jnius.JavaMultipleMethod.__call__ (jnius/jnius.c:21688)
jnius.JavaException: No methods matching your arguments
>>>

This is because the Java ArrayList has multiple methods which could be accepted for a simple int value.

As for the rest of this example I’ll stay with my Java class which adds up numbers provided. The Java code is the same as it is in the JPypeExample — only the class name is changed:

public class PyJNIusExample {

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

After compiling the code (with javac) I tried to load the class in Python:

>>> from jnius import *
>>> autoclass("PyJNIusExample")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Python/2.7/site-packages/jnius/reflect.py", line 162, in autoclass
    c = find_javaclass(clsname)
  File "jnius_export_func.pxi", line 23, in jnius.find_javaclass (jnius/jnius.c:12356)
jnius.JavaException: Class not found 'PyJNIusExample'

As you can see it yielded an exception. First I thought that the class path in PyJNIus does not include the current folder (“.”) but it does. The problem was the Java compiler version — the same problem as with JPype: I switched to the folder containing the JPype examples, started python and autoclassed the JPypeExample:

>>> from jnius import autoclass
>>> autoclass("JPypeExample")
<class 'jnius.reflect.JPypeExample'>

Yes, this worked so PyJNIus loads the current folder into the class path — only the version was problematic. So I recompiled the class with

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

and now the PyJNIusExample works too. I added the class to the GitHub repository too as I did with the JPype example.

If you wonder how to enable access to other Java classes not located in the current folder: I do not know it either. I found an example to set the class path in your current environment but it does not seem to work: I get the same class not found exception as with the 1.7-compiled class.

So now we have a Java class compiled with 1.6, let’s see how to call the methods inside.

First of all let’s invoke the method which takes 4 int parameters:

>>> from jnius import autoclass
>>> PyJNIusExample = autoclass("PyJNIusExample") # load the class
>>> pyJNIusExample = PyJNIusExample() # intantiate the loaded class
>>> pyJNIusExample.addNumbers(13,1,20,8)
42

As you can see there is nothing complex in using PyJNIus. The second example will be the other method: addition. As you know you can call static classes from instantiated objects too:

>>> pyJNIusExample.addition(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius_export_class.pxi", line 556, in jnius.JavaMethod.__call__ (jnius/jnius.c:18541)
  File "jnius_conversion.pxi", line 96, in jnius.populate_args (jnius/jnius.c:6724)
  File "jnius_conversion.pxi", line 527, in jnius.convert_pyarray_to_java (jnius/jnius.c:10422)
jnius.JavaException: ('Invalid variable used for L array', 'Ljava/lang/Integer;', (1, 2))

Whoops what is this error message? It tells that the called method “addition” awaits Integer objects as parameters. This can be solved easily: just autoclass the Integer class and create your numbers as Integer variables:

>>> Integer = autoclass("java.lang.Integer")
>>> one = Integer(1)
>>> two = Integer(2)
>>> pyJNIusExample.addition(one, two)
3

If you want to use the simple int version, you can do it however be aware that Java let you do this (if you do not call the method):

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

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

but it will lead to problems. If you create a main method in your java class and try to compile it:

    public static void main(String... args) {
        PyJNIusExample.addition(1,2,3);
    }

you get the following error message:

PyJNIusExample.java:27: reference to addition is ambiguous, both method addition(java.lang.Integer...) in PyJNIusExample and method addition(int...) in PyJNIusExample match
        PyJNIusExample.addition(1,2,3);
                      ^
1 error

And this is OK because Java really cannot distinguish between the two versions of the methods because an Integer can be boxed into an int and vice-versa.

Because Java let you compile the simple class without a method calling your addition functions you can call the methods from PyJNIus too — with an error message:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius_export_class.pxi", line 824, in jnius.JavaMultipleMethod.__call__ (jnius/jnius.c:21688)
jnius.JavaException: No methods matching your arguments

If you remove the method with the Integer arguments you can call the method with int variables without autoclassing the Integer class:

>>> pyJNIusExample.addition(1,2,3)
6

But if you try to call the int method with Integer objects: you get an error too:

>>> Integer = autoclass("java.lang.Integer")
>>> one = Integer(1)
>>> py.addition(one,one)
Traceback (most recent call last):
File "", line 1, in
File "jnius_export_class.pxi", line 556, in jnius.JavaMethod.__call__ (jnius/jnius.c:18541)
File "jnius_conversion.pxi", line 96, in jnius.populate_args (jnius/jnius.c:6724)
File "jnius_conversion.pxi", line 469, in jnius.convert_pyarray_to_java (jnius/jnius.c:9882)
TypeError: an integer is required

So you can see, that PyJNIus has some problems with boxing and unboxing primitive types. And it has problems with multiple methods with the same name (and I do not mean method overloading). For example you can define two methods with the same names as listed below and compile your class. Because it is no overloaded method (they have different return types and parameters) Java lets you compile the class but PyJNIus raises an exception because you have multiple methods with the same name.

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

    public static String addition(String value) {
        return value;
    }
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius_export_class.pxi", line 824, in jnius.JavaMultipleMethod.__call__ (jnius/jnius.c:21688)
jnius.JavaException: No methods matching your arguments

What about calling a static method on the class itself?

Stuck with the same example let’s call the methods on the loaded class (currently the PyJNIusExample variable in our Python application). You can wonder: in this case PyJNIus does not find two methods with the same name:

>>> from jnius import autoclass
>>> PyJNIusExample = autoclass("PyJNIusExample")
>>> PyJNIusExample.addition(13,1,20,8)
42
>>> PyJNIusExample.addition("Hello World")
'Hello World'

What about overloading? It works too: you can call those static methods without a problem. So the problem lies somewhere in an instantiated object in the depths of PyJNIus.

I showed the examples here from the Python console however the whole script of the example is included in the repository as a single file.

Conclusion

As you could see, there are fewer tools to call a Python library in Java as call Java from Python. This could be because of Android which is Java based and Python developers seek some ways to develop Python apps for Android (however I’ve seen a book about creating android apps with Python but it was using de SL4A the Scripting Layer for Android, to work around learning the Java language and dive into Android development with Python — however I did not read the book because I cannot afford to buy it currently).

And probably you can guess that I could write one post about each utility not squeezing two of them together in one post restricting the examples to some minor ones. If you would like to see more examples of a given tool just write me a message (eventually include the topic you are interested in with the specific tool) and I’ll create a post about it (I do not know my future schedule but I’ll say I’ll do it in one month). And this is a long-time offer: if you read this article in 2020 (or 2044) and you have a question you can write too — and I’ll see which distributions are still available or what other tools exist.

Which would I use in the future?

For calling Python in a Java application there is no favorite tool. Because of what I learned from the previous article. You always have to choose what do you really need and what you have. If you need a simple Python interpreter you can use Jython. If you need some other modules and have Python installed you use one of the old-school variations. If you have to develop an application for a server where the app is only deployed and you need custom Python modules — you give up 🙂 Or I suggest you to look at other alternatives or take some action if the Java code is open sourced.

For calling Java from Python scripts PyJNIus is worth to give it a try in real environments because it knows var-args which can be a pain in JPype, but if you rely on method-overloading than this tool can be a pain too.

The source code

As usual I have set up a GitHub repository for this experiment.

The Python sources are written for Python 2.7.

If you have plans to use the scripts with Python 3.3 I have to reference to a previous post where I fight with some new-old features between Python 2.7 and 3.3 because I want a script which runs on both version.

Jython

I guess I have to leave Jython out of this post because I run out of time. Perhaps later at some point I’ll include another article containing the Java-calling functionality of Jython because it is an equal tool for calling Java from Python.

GHajba
 

Senior developer, consultant, author, mentor, apprentice.

I love to share my knowledge and insights what I achieve through my daily work which is not trivial — at least not for me.

Click Here to Leave a Comment Below 1 comments
%d bloggers like this: