IT|軟體|程式語言|Java 調用 Python 使用 Jpython

極少數時候,我們會碰到類似這樣的問題:與 A 合作寫代碼, A 只會寫 Python,不熟悉 Java ,而你只會寫 Java 不擅長 Python,並且發現難以用 Java 來重寫對方的代碼,這時,就不得不想方設法「調用對方的代碼」。

下面舉一些簡單的小例子,借此說明:如何在 Java 中調用 Python 代碼。

Maven 使用 Jpython
<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython-standalone</artifactId>
    <version>2.7.1</version>
</dependency>

HelloPython 程式
import org.python.util.PythonInterpreter;

public class HelloPython {
    public static void main(String[] args) {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("print('hello')");
    }
}
什麼是 PythonInterpreter 呢?它的中文意思即「 Python 解釋器」。我們知道 Python 程序都是通過解釋器執行的,上面的代碼就是在 JVM 中創建一個「 Python 解釋器」對象,模擬 Python 解釋器的行為,通過 exec(" Python 語句") 直接在 JVM 中執行 Python 代碼,代碼的輸出結果為:hello,需要提醒各位的是,該程序運行速度相較正常的 Java or Python 程序都要慢那麼一點。

JVM 中執行 Python 腳本
interpreter.execfile("D:/labs/mytest/hello.py");  
如上,將 exec 改為 execfile 就可以了。需要注意的是,這個 .py 文件不能含有第三方模塊,因為這個「 Python 腳本」最終還是在 JVM 環境下執行的(而非依賴於本地計算機環境),如果 .py 程序中有用到第三方模塊(例如 NumPy)將會報錯:java ImportError: No module named xxx

JVM 中調用 Python 編寫的函數
def hello():
    return 'Hello'

Java 代碼中調用這個 Python 函數:
import org.python.core.PyFunction;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;

public class HelloPython {
    public static void main(String[] args) {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.execfile("D:/labs/hello.py");

        PyFunction pyFunction = interpreter.get("hello", PyFunction.class); //  第一個參數為期望獲得的函數(變量)的名字,第二個參數為期望返回的對象類型
        PyObject pyObject = pyFunction.__call__(); //  調用函數

        System.out.println(pyObject);
    }
}
上面的代碼執行結果為:Hello
即便只是調用一個函數,也必須先加載這個 .py 文件,之後再通過 Jython 包中所定義的類獲取、調用這個函數。

如果函數需要參數,在 Java 中必須先將參數轉化為對應的「 Python 類型」,例如:
__call__(new PyInteger(a), new PyInteger(b))
ab的類型均為 Java 中的 int 型,還有一些 Jython 類型諸如:PyString(String string)PyList(Iterator<PyObject> iter) 等,詳細信息可以參考官方的 api 文檔。

在本地環境中調用 Python 腳本
由於 Jython 運行過慢並且不支持第三方的 Python 模塊,通過 Java 代碼執行一段終端命令來調用 Python 腳本,根據具體問題決定如何交互可能才是實際中真正會用到的方式(詳見下面的舉例代碼)。
舉例代碼:下面是和捨友合作寫的一個小程序(可以識別很粗的手寫數字),界面上引用了 core java 上的一段代碼:
import java.io.*;

class PyCaller {
    private static final String DATA_SWAP = "temp.txt";
    private static final String PY_URL = System.getProperty("user.dir") + "\\test.py";

    public static void writeImagePath(String path) {
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileWriter(new File(DATA_SWAP)));
        } catch (IOException e) {
            e.printStackTrace();
        }

        pw.print(path);
        pw.close();
    }

    public static String readAnswer() {
        BufferedReader br;
        String answer = null;
        try {
            br = new BufferedReader(new FileReader(new File(DATA_SWAP)));
            answer = br.readLine();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return answer;
    }

    public static void execPy() {
        Process proc = null;
        try {
            proc = Runtime.getRuntime().exec("python " + PY_URL);
            proc.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //  測試碼
    public static void main(String[] args) throws IOException, InterruptedException {
        writeImagePath("D:\\labs\\mytest\\test.jpg");
        execPy();
        System.out.println(readAnswer());
    }
}
運行流程:Java Swing 界面接收用戶輸入 --> Java 將用戶輸入寫到本地文件中 --> Java 調用本地 Python 腳本 --> Python 從本地文件拿到用戶輸入 --> Python 處理用戶輸入得到最終結果 --> Python 把最終結果寫到本地文件 --> Java Python 腳本的調用結束 --> Java 從本地文件中取出最終結果 --> Java 把最終結果返回給用戶


[參考資源]

留言

這個網誌中的熱門文章

IoT|硬體|樹莓派|外接麥克風及喇叭設置

成長|語文|學習-英文 持續更新!

IoT|硬體|通訊|Arduino 使用 SoftwareSerial Library 與電腦通訊