ClojureからPythonを使う

背景

プログラミング言語の中でも特によく使われているのは、Java・JavaScript・Pythonです(恣意的)。

これらの言語をClojureでまとめて書くことができれば楽ちんです。

Pythonはlibpython-cljというライブラリを使えば、ClojureからPythonを使うことができてしまいます。

libpython-clj

開発環境として、以下を使用しています。

  • macOS(Sonoma)
  • pyenv

Clojureからlibpython-cljによってPythonを使うにはPythonの動的ライブラリ(libpython.dylib)とバイナリ(python)が必要なようです。

libpython-cljはそれらの場所を明示しなくてもある程度自動で探してくれるようなので開発環境によっては、

  • インストールした終わり!
  • ブログ記事終わり!

にできるのですが自分の環境では問題が発生しました。

問題と解決

ClojureのREPLで以下のコードを試すとエラーが発生してしまいました

user=> ;; 問題1
user=> (require '[libpython-clj2.require :refer [require-python]])
Execution error at libpython-clj2.python/initialize! (python.clj:129).
Failed to find a valid python library!

user=> ;; 問題2
user=> (py/initialize! :python-executable "/Users/watanany/.pyenv/versions/3.11.5/bin/python3.11" :library-path "/Users/watanany/.pyenv/versions/3.11.5/Python")
Syntax error compiling at (insn/util.clj:105:1).
Unable to find static field: ACC_OPEN in interface org.objectweb.asm.Opcodes

問題1: 自動で動的ライブラリやバイナリを見つけてくれない

こちらの方は、Python動的ライブラリ(libpython.dylib)が見つからないという意味でした。

libpython.dylibが単に見つけられないのか、そもそもインストールされていないのか、自分が使っているPythonの環境で調査する必要がありました。

macos - How do I find where Python's libpython is located? - Stack Overflow

$ ls -l $(python3 -c 'from distutils.sysconfig import get_config_var; print(get_config_var("LIBDIR"))')

でlibpython.dylibがありそうなディレクトリを調べたところ、Pythonの静的ライブラリ(libpython.a)はあったのですが、動的ライブラリ(libpython.dylib)は存在しませんでした。


pyenvでlibpython.dylibをインストールする方法を調べると、関連してそうなISSUEを見つけることができました。

ISSUEにあるコマンド通り叩くとビルド中にtkinterがない旨のエラーが出てきたので、tkinterをbrewでインストールしつつ、Pythonをインストールしました。

$ brew install python-tk@3.11
$ env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install -v 3.11.5

もう一度Pythonをインストールすると無事libpython.dylibが作られていました。

問題2: Javaのエラーが出る

問題2の方も動的ライブラリが見つけられないことが原因のエラーかと思ったのですが、いつも使っているleiningenのprofileの:dependenciesに古い依存ライブラリが含まれていたためでした。 (ChatGPTからヒントをいただいた)

どの依存ライブラリがエラーを出しているかわからなかったので、一旦、:dependenciesをlibpython-cljのみにしたところ解決することができました。

;; profiles.clj

{:py
 {:plugins [[lein-ancient "0.6.15"]
            [lein-exec "0.3.7"]
            [lein-pprint "1.3.2"]
            [lein-localrepo "0.5.4"]]
  :dependencies [[clj-python/libpython-clj "2.025"]]
  :injections [(require '[clojure.repl :refer :all])
               (require '[clojure.pprint :refer :all])
               (require '[libpython-clj2.python :refer [py. py.. py.-] :as py])
               (require '[libpython-clj2.require :refer [require-python]])
               ]}}

ClojureからPythonを実行

;; /tmp/clj.clj

(require '[libpython-clj2.require :refer [require-python]])
(require '[libpython-clj2.python :refer [py. py.. py.-] :as py])
(require-python '[pandas :as pd])

(def df
  (pd/DataFrame {:A [0 1 2] :B [3 4 5] :C [6 7 8]}))

(println df)
;; Output:
;;    A  B  C
;; 0  0  3  6
;; 1  1  4  7
;; 2  2  5  8

(println
 (:A (py. df to_dict)))
;; Output:
;; {0: 0, 1: 1, 2: 2}

実行すると無事動いてくれました!

$ PYENV_VERSION=3.11.5 lein with-profile +py repl
user=> (load-file "/tmp/clj.clj")
   A  B  C
0  0  3  6
1  1  4  7
2  2  5  8
{0: 0, 1: 1, 2: 2}
nil