Programming/Python

Embed Python in java (jep) 환경 설정

라우드니스 2017. 4. 5. 01:17

Embed Python in Java (jep)


  jep는 간략하게 설명하면 java로 python interpreter를 구현하여 C / C++ 기반의 library와는 호환이 어려웠던 jython과는 다르게, JNI를 통해 CPython을 호출함으로써 C / C++ 기반의 python library도 java 상에서 실행이 가능한 프로젝트이다. 


Getting-Started 를 따라서 설치를 진행하다보면, 별도의 python library 사용 없는 간단한 python 코드의 경우 쉽게 실행 가능하다


Main.java:

import jep.Jep;

public class Main {
  public static void main(String[] args) throws Exception{
    try(Jep jep = new Jep(false)) {
      jep.eval("from java.lang import System");
      jep.eval("s = 'Hello World'");
      jep.eval("System.out.println(s)");
      jep.eval("print(s)");
      jep.eval("print(s[1:-1])");
    }
  }
}


compile 및 실행

javac -cp /usr/local/lib/python2.7/dist-packages/jep/jep-3.6.3.jar Main.java
java -cp /usr/local/lib/python2.7/dist-packages/jep/jep-3.6.3.jar:. -Djava.library.path=/usr/local/lib/python2.7/dist-packages/jep Main


실행 결과:

Hello World
Hello World
ello Worl

그러나, Numpy와 같은 외부 라이브러리를 사용하려고 하면, 다음과 같은 에러를 확인할 수 있다:

Exception in thread "main" jep.JepException: :
Importing the multiarray numpy extension module failed.  Most
likely you are trying to import a failed build of numpy.
If you're working with a numpy git repo, try `git clean -xdf` (removes all
files not under version control).  Otherwise reinstall numpy.

        at /usr/local/lib/python2.7/dist-packages/numpy/core/__init__.(__init__.py:24)
        at /usr/local/lib/python2.7/dist-packages/numpy/lib/type_check.(type_check.py:11)
        at /usr/local/lib/python2.7/dist-packages/numpy/lib/__init__.(__init__.py:8)
        at /usr/local/lib/python2.7/dist-packages/numpy/add_newdocs.(add_newdocs.py:13)
        at /usr/local/lib/python2.7/dist-packages/numpy/__init__.(__init__.py:142)

 당연히 Numpy는 정상작동 하고 있는 상태여서 왜 이런 문제가 발생할까, 어떻게 해결할까 고민했는데 문제자체는 jep 가 포함된 java를 실행할 때 환경 변수의 문제였다.

(* jep github page의 버그 리포팅을 보면 python 2.7.11 에서는 문제가 있을 수도 있으므로, 2.7.10 이나 2.7.12 혹은 더 높은 버전을 사용하는 것을 추천한다.)


다음과 같이 실행 전에 환경변수를 주면 위와 같이 library import 시 제대로 못읽어오는 문제는 해결할 수 있다:

LD_LIBRARY_PATH="/usr/lib:/usr/local/lib/python2.7/dist-packages/"; export LD_LIBRARY_PATH
LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libpython2.7.so"; export LD_PRELOAD
java -cp /usr/local/lib/python2.7/dist-packages/jep/jep-3.6.3.jar:. -Djava.library.path=/usr/local/lib/python2.7/dist-packages/jep Main


참고로, LD_LIBRARY_PATH와 LD_PRELOAD에 사용하는 python library 및 so 파일의 경로는 리눅스 배포판 마다 다르다.

나와 같이 python은 잘 사용하지 않아 아무것도 모르는 상태라면, 개인적으로 numpy와 같은 library가 잘 작동하고 있는지도 확인할 겸 jep test를 진행하는것을 추천한다. 


상단의 jep의 github에서 해당 git 을 가져온 후, test를 진행해 보자:


python setup.py test


빌드 메시지들도 나오면서, 정상적으로 작동한다면 다음과 같이 끝나는 메시지를 얻을 수 있다:

$python setup.py test
numpy include found at /usr/local/lib/python2.7/dist-packages/numpy/core/include
running test
running build
running setup_java
running build_java
running build_javah
running build_jar
running build_py
running build_ext
running build_scripts
ln -sf jep.so build/lib.linux-x86_64-2.7/libjep.so
....................................................................................................................ssssssssssss......................
----------------------------------------------------------------------
Ran 150 tests in 1.869s

OK (skipped=12)

나의 경우 이미 실행을 해 빌드 메시지는 나오지 않았다.


test가 끝났다면 jep 디렉토리 내에 build 디렉토리가 생기고, build/scripts-{python.version}/jep 스크립트를 열어보면 다음과 같이 생겼다:


#!/bin/sh

VIRTUAL_ENV=
export VIRTUAL_ENV

LD_LIBRARY_PATH="/usr/lib:/usr/local/lib/python2.7/dist-packages/"; export LD_LIBRARY_PATH
LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libpython2.7.so"; export LD_PRELOAD

if test "x$VIRTUAL_ENV" != "x"; then
  PATH="$VIRTUAL_ENV/bin:$PATH"
  export PATH
  PYTHONHOME="$VIRTUAL_ENV"
  export PYTHONHOME
fi

cp="/usr/local/lib/python2.7/dist-packages/jep/jep-3.6.3.jar"
if test "x$CLASSPATH" != "x"; then
    cp="$cp":"$CLASSPATH"
fi

jni_path="/usr/local/lib/python2.7/dist-packages/jep"

args=$*
if test "x$args" = "x"; then
  args="/usr/local/lib/python2.7/dist-packages/jep/console.py"
fi

exec java -classpath "$cp" -Djava.library.path="$jni_path" jep.Run $args


해당 스크립트를 참조하면 자신의 환경에서 LD_LIBRARY_PATH, LD_PRELOAD, -Djava.library.path, -classpath 에 어떤 파일 및 경로를 지정해야 하는지 쉽게 알 수 있다. 



반응형