Expert tips on writing Python programs tailored to run on Jython, making the best use of Jython's features and Java interop.
This is a document with tips and usage details about Jython that I’ve come across. I intend to document handy features of Python as well as some clever inter-op facilities provided by Jython.
I’m going to assume you’re not a complete beginner to Java and Python languages. If you find anything off or have a suggestion to add, please do write to me. Thanks!
Logging and Printing¶
When using Apache’s log4j, we can get an instance of a Logger using the API just as we would in
Java:
>>> from org.apache.log4j import Logger
>>> log = Logger.getLogger('jython_script')
When getting a Logger instance for a module that is imported, a logger with a category specific to
that module can be obtained using the following code:
log = Logger.getLogger(__name__)
The __name__ name is a variable containing the current module’s name as a string. Note that
__name__ is set to the string '__main__' if the module is run as a script and not imported from
another script. This should be kept in mind when using the above code.
The standard printing functions of Java can be imported into Python and used directly in the following way:
>>> from java.lang import System
>>> System.out.println('Hola')
Hola
>>> System.err.println('Hello there')
Hello there
>>> System.out.print('Hola\n')
Hola
However, it’s usually more convenient to use Python’s print statement to output things to standard
output and error:
print 'Hello world!'
Here’s a table illustrating the print statement equivalents of the Java print* functions:
| Java | Python |
|---|---|
System.out.println("!") |
print '!' |
System.out.print("!") |
print '!', |
System.err.println("!") |
print >> sys.stderr, '!' |
System.err.print("!") |
print >> sys.stderr, '!', |
Bean Properties¶
Jython can implicitly call the .get* and .set* methods that are widely used in Java classes to
get and set the values of instance attributes. Here’s an illustration of how this inter-op works:
| Jython | Java equivalent |
|---|---|
obj.somePropertyValue |
obj.getSomePropertyValue() |
obj.somePropertyValue = 123 |
obj.setSomePropertyValue(123) |
Of course, when such .get* and .set* methods are not available, this falls back gracefully to
trying get/set the property values directly, just as Java would treat those statements.
Strings¶
Strings in Java (i.e., objects of type java.lang.String) are converted to unicode objects when
passed in to Python world. Whereas str and unicode objects in Python are converted to
java.lang.String instances when passed in to Java world. This conversion is seamless and we
usually don’t have to worry about it.
However, if needed, we can explicitly create an instance of java.lang.String from a unicode
object in Python:
>>> from java.lang import String
>>> greeting = String('Hello')
>>> greeting
Hello
>>> type(greeting)
<type 'java.lang.String'>
String formatting using % operator in Python cannot be applied to Java String objects. They have
to converted to str or unicode first.
Maps as Dictionaries¶
For the purposes of the following examples, let’s work with the following
Map:
java.util.Map<String, Integer> data = new java.util.HashMap<>();
data.put("a", 1);
data.put("b", 2);
data.put("c", 3);
Maps support the getitem syntax very well so it is usually convenient to think of them as
python-style dictionaries. Here’s an example:
>>> print data['a'] # data.get("a")
1
>>> print data['b'] # data.get("b")
2
>>> data['d'] = 4 # data.put("d", 4)
>>> data['d'] # data.get("d")
4
>>> len(data) # data.size()
4
>>> 'c' in data # data.containsKey("c")
True
>>> del data['c'] # data.remove("c")
>>> 'c' in data # data.containsKey("c")
False
>>> data
{a=1, b=2, d=4}
>>> len(data) # data.size()
3
Although this resembles the usage of a traditional python dictionary, the methods you’d expect in a
dictionary are not all available. This is a Map object after all and it has the methods of the
Map class. However, it is easy to get see the parallels among some of the most used methods.
dict method |
Map method |
|---|---|
.keys |
.keySet |
.values |
.values |
.clear |
.clear |
.items (gives 2-tuples) |
.entrySet (gives Entry objects with .key and .value) |
.update |
.putAll (accepts dict as well as a Map) |
The dict builtin can be called on the Map object to get a python-style dictionary, if needed.
Additionally, just like a python dictionary, calling list (or set) on the Map object gives a
list (or set) of the keys in the Map.
Using for loops to iterate over Maps yields the keys in the Map, which is consistent with how
for loops work with python dictionaries.
for key in data:
print key, data[key]
Prints the following:
a 1
b 2
d 4
In python, the .items method returns each entry as a tuple which lets us write the for loop like
the following:
# !!! Only works if `data` is a python-style dictionary, not if it is a `Map`.
for key, value in data.items():
print key, value
But unfortunately, since Map doesn’t have the .items method, this is not possible. However, we
can use the .entrySet method to construct something slightly similar.
for entry in data.entrySet():
print entry.key, entry.value
To iterate over the values of a Map, since the method is called .values in both dict and
Map, the same piece of code would work with any object.
for value in data.values():
print value
Empty Map objects are treated as False in boolean contexts, just as with python’s dictionaries.
Collections¶
The two main collection types in Python are list and set. The equivalents in java are the
interfaces List and
Set. Let’s prepare some data for
our examples.
java.util.List<String> planets = new java.util.ArrayList<>();
planets.add("Mercury");
planets.add("Venus");
planets.add("Earth");
java.util.Set<String> colors = new java.util.HashSet<>();
colors.add("White");
colors.add("Black");
colors.add("Red");
colors.add("Green");
colors.add("Blue");
The getitem syntax can be used with Lists seamlessly:
>>> planets[0]
u'Mercury'
>>> planets[1]
u'Venus'
The slicing syntax, returns Lists of the same type, not python-style lists.
>>> planets[:2]
[Mercury, Venus]
>>> type(_) # `_` is a variable set to the return value of last expression.
<type 'java.util.ArrayList'>
>>> planets[::-1]
[Earth, Venus, Mercury]
>>> type(_)
<type 'java.util.ArrayList'>
However, the getitem syntax is not supported for Sets as it doesn’t make sense there since
Sets are unordered collections. But the operator support available for sets in python are
available with Java Set objects as well.
>>> 'Red' in colors
True
>>> len(colors)
5
The for loop can be used on any
Collection type objects to
iterate over the object’s contents.
>>> for x in planets:
... print x
...
Mercury
Venus
Earth
>>> for x in enumerate(planets):
... print x
...
(0, u'Mercury')
(1, u'Venus')
(2, u'Earth')
Here’s equivalents for some of the methods available in Java’s Collections and Python’s collection
types.
| Java | Jython |
|---|---|
Collection.add |
list.append / set.add |
Collection.addAll |
list.extend / set.update (Prefer list + list or set.union) |
Collection.contains |
in list or in set |
Collection.isEmpty |
bool(list) or bool(set) (Can be used directly in a boolean context) |
Collection.size |
len(list) or len(set) |
Empty Collections are treated as False in boolean contexts, just as with python’s collections.
Java Arrays¶
Just as Java’s List is mirrored in Python with list, Java’s arrays are mirrored using the array
structure available in Jython’s array module.
That official documentation is quite exhaustive on this topic, so I suggest going over it to get an
idea of handling arrays in Jython.
The Iteration Protocol¶
Java’s Iterator style
iteration is supported by Jython’s for statements. For example, consider the following Java
Iterator that’s trying to emulate a small fraction of Python’s range function:
package ssk.experiments;
import java.util.Iterator;
public class RangeIterator implements Iterator<Integer> {
private Integer current = 0, max;
public RangeIterator(int max) { this.max = max; }
@Override
public boolean hasNext() { return current < max; }
@Override
public Integer next() { return current++; }
}
Since classes are instantiated without a new keyword in Python, combined with the fact that
Jython’s for statement supports Java’s Iterators, we can use the above in the following way:
from ssk.experiments import RangeIterator
for n in RangeIterator(5):
print n
This gives the following output:
0
1
2
3
4
Since Jython’s for statement supports iterating over Java’s
Enumeration type, the
above same for loop would work with a RangeEnumeration class as defined below:
package ssk.experiments;
import java.util.Enumeration;
public class RangeEnumeration implements Enumeration<Integer> {
private Integer current = 0, max;
public RangeEnumeration(int max) { this.max = max; }
@Override
public boolean hasMoreElements() { return current < max; }
@Override
public Integer nextElement() { return current++; }
}
Jython seamlessly handles the getting of an instance of an Iterator from a Java
Iterable. This is actually
how the for statement works with the List and Set collections discussed earlier (Collection
is a sub-interface of Iterable).
Patching Java Classes¶
In Python, new methods and attributes can be added to existing classes. This comes from the dynamic nature of the programming language and the runtime. The JVM is also a dynamic runtime, but the Java language doesn’t allow us to modify existing classes. This is where Jython comes in. Jython lets us add and override methods on existing Java classes. Although this is seldom needed, this can illustrate the extent of Jython’s integration with the JVM.
Here’s a Java class:
package ssk.experiments;
import java.util.List;
public class Country {
private String name;
public Country(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
There’s nothing fancy with the above class. It’s a regular class with one property with a .get and
.set methods. Now, let’s add a new method to this class.
from ssk.experiments import Country
def upcase(self):
self.name = self.name.upper()
Country.upcase = upcase
# Create a `Country` object and call `upper_name` method.
largest_country = Country('Russia')
largest_country.upcase()
print largest_country.name
This would print RUSSIA, as expected.
Note that this is an advanced feature and should be used with caution. In almost all cases, it is probably a better idea to modify the original Java class definition directly. But when that is not an option, creating a simple Python function that works with these objects should be considered. Modifying existing classes should only be used as a last resort.
Operator Overloading¶
One nice and practical case for adding methods on existing Java classes is to leverage Python’s
support for operator overloading with Java classes. One good example for this is with the
BigDecimal class. Mathematical operations on objects of BigDecimal are provided as individual
methods like .add, .subtract etc. We can add operator support (in Jython) for these objects
by adding the appropriate methods to the BigDecimal class.
For instance, here’s how we can add support for the + operator:
from java.math import BigDecimal
BigDecimal.__add__ = lambda self, other: self.add(other)
print BigDecimal(42) + BigDecimal(10)
This would print 52, as expected. More methods can be added to support all the mathematical
operators such as __sub__ for subtraction and __mul__ for multiplication etc. The full list of
such method names can be found on the official data model documentation
page.
Conclusion¶
This is not intended to be an exhaustive guide to what Jython can do. I hoped to give you a taste of how well Jython handles inter-op with Java and hopefully I’ve helped you write better Python - Java inter-op code. Thank you and any suggestions and feedback are very welcome.