Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure Jython interop

I was wondering if anyone has tried somehow calling Jython functions from within Clojure, and how you went about doing this if so. I have not used Jython, but I would imagine the Jython interpreter can be invoked in the same way as any other java code, and Python programs can be run within it. However I wonder if it would be possible to somehow call individual python functions from Clojure. Like I said, I have not tried this yet, so it might actually be straightforward and obvious. I'm just wondering if anyone has tried doing this.

Thanks, Rob

like image 245
rplevy Avatar asked Jan 24 '10 22:01

rplevy


1 Answers

A note: I just realised that the question is specifically about calling Jython functions from Clojure and not about building a full-fledged Jython-Clojure interop solution... But! I've already produced a smallish write-up on my initial thoughts on the latter and I guess that's the logical next step anyway. I mean, how'd you go about using interesting Python packages without reasonably convenient access to Python classes? Writing Python functions to wrap method calls and the like is a possible idea... but rather a horrible one. So here goes anyway.

For basic calling of Jython-executed Python functions from Clojure read the second paragraph below this point and the code snippets. Then read the rest for fun and unanticipated profit.

I think that the experience would initially be far from seamless... In fact, my prediction would be that smoothing out the bumps could really be a royal pain. Still, I have a hunch it could actually be easier then calling into Jython from Java. Just a longish €0.02 from me... May someone more knowledgeable come and show me I don't know what I'm talking about. ;-)

The first thing to notice is that Jython wraps everything in its own classes, all deriving from org.python.core.PyObject, doesn't bother to make Python callables Callable or Runnable etc. This might actually not be too much of a problem with some multimethod / macro wrappers.

Python classes can be used from Java, but my (possibly flawed) understanding is that normally, when trying to act upon Jython-made instances of Python classes, Java code only sees the methods inherited from a Java base class or interface... Otherwise a specially formatted docstring (!) is required. Here's a link to the relevant page on the JythonWiki. (No idea how up-to-date it is.) The cool thing is, apparently a PyObjectDerived (an instance of a user-defined Python class) can be convinced to call its methods with the given arguments. So, with a dilligent wrapping effort, one might hope to be able to use somewhat bearable syntax to do it.

In fact, let's see some code:

;; a handy instance of PythonInterpreter...
(def python (org.python.util.PythonInterpreter.))
(.eval python "5")
; -> #<PyInteger 5>

Well, things are wrapped. A fun Clojuresque unwrapper:

(defmulti py-wrap class)
;; but let's not wrap if already a PyObject...
(defmethod py-wrap org.python.core.PyObject [pyo] pyo)
(defmethod py-wrap Integer [n] (org.python.core.PyInteger n))
(defmethod py-wrap Long [n] (org.python.core.PyLong n))
(defmethod py-wrap BigInteger [n] (org.python.core.PyLong n))
(defmethod py-wrap String [s] (org.python.core.PyString s))

And a counterpart to the above:

(defmulti py-unwrap class)
;; if unsure, hope it's not a PyObject at all...
(defmethod py-unwrap :default [x] x)
(defmethod py-unwrap org.python.core.PyInteger [n] (.getValue n))
(defmethod py-unwrap org.python.core.PyString [s] (.toString s))

The functions: You can .__call__ them and you can ._jcall them. The latter option is somewhat more pleasing, as it accepts a Java array of regular Java objects, although it still returns a PyObject. The former takes an appropriate number of positional arguments which should already by PyObjects. I've no idea how to pass in keyword arguments... though Jython does that somehow, so there must be a way.

Here's an ultra-basic helper for ._jcall-type calls:

(defn py-call [pyf & args]
  (apply (fn [pyf & args] (._jcall pyf (into-array args)))
         (map #(if (string? %) (py-eval %) %)
              (cons pyf args)))

You can .exec a string containing a Python definition of fact, then do (py-call "fact" 10) to get a #<PyInteger 5> back; unwrap if you feel like it.

And so on and so forth... What I don't know is:

  1. What kind of effort would be needed to make this useful enough to interface interesting Clojure code with interesting Python code.
  2. Would providing a reasonable syntax for calls into Python necessitate doing anything really bad for performance.
like image 66
Michał Marczyk Avatar answered Oct 31 '22 19:10

Michał Marczyk