Friday, May 30, 2008

Jython: How to Instantiate Classes Written in Python Code

This week, as part of my GSoC project, I had to do some work related with zxJDBC, the very cool DBAPI <-> JDBC brige which is bundled with Jython. Among other things, I'm improving the default type mapping, adding support for converting java.sql.* instances to pythonic datetime.* ones.

As the type mapping is written inside a class written in Java, I was confronted to the problem of how to instantiate datetime.* objects, which (by now) Jython implements using pure python code. The answer was very simple: Just do by hand what Python always does when you write:

import datetime
datetime.date(year, month, day)

You know, the import statement is implemented by the __import__ builtin, foo.bar is getattr(foo, 'bar') and f(x) is f.__call__(x). Then, the following is equivalent to the previous snippet:

datetime = __import__('datetime')
getattr(datetime, 'date').__call__(year, month, day)

Which, translated to Java/Jython looks almost the same:

PyObject datetime = __builtin__.__import__('datetime')
datetime.__getattr__('date').__call__(Py.newInt(year),
Py.newInt(month),
Py.newInt(day))

Once you get the idea, not only instantiating, but doing anything with classes written with Jython from Java code looks like a piece of cake.

Hey!, How easiest could it be?

Note that this works if all the Jython machinery is in place. This will be the case if your Java code is being called (directly or indirectly) from python code being ran on Jython. As any Jython module, such as zxJDBC, is always used from python code, this is something to have in mind when writing them in Java.

2 comments:

Nicholas said...

BTW, apparently .invoke() is preferable to .__getattr__().__call__(). See pjenvey's fix for my commit r5070. (Since I actually used your weblog entry to figure out how to do this, I figured it would make sense to post a correction here. :-)

Leo Soto M. said...

Hmm, but I see that we have no invoke() shortcut for 3 args (as opposed to the 3-args __call__). Maybe it is just a matter of adding it?

BTW, the javadoc of invoke() sort of over-specify it, by saying that it calls a method on a PyObject. By looking at the implementation it will also work on my case, where I need to instantiate a class which is an attribute of its module.

Or maybe this over-specification is intentional, in order to allow future optimizations?