在Java中調用Python

寫在前面

在微服務架構大行其道的今天,對於將程序進行嵌套調用的作法其實並不可取,甚至顯得有些愚蠢。固然,之因此要面對這個問題,或許是由於一些歷史緣由,或者僅僅是爲了簡單。剛好我在項目中就遇到了這個問題,須要在Java程序中調用Python程序。關於在Java中調用Python程序的實現,根據不一樣的用途可使用多種不一樣的方法,在這裏就將在Java中調用Python程序的方式作一個總結。html

直接經過Runtime進行調用

咱們知道,在Java中若是須要調用第三方程序,能夠直接經過Runtime實現,這也是最直接最粗暴的作法。java

public class InvokeByRuntime {
    /**
     * @param args
     * @throws IOException 
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws IOException, InterruptedException {
        String exe = "python";
        String command = "D:\\calculator_simple.py";
        String num1 = "1";
        String num2 = "2";
        String[] cmdArr = new String[] {exe, command, num1, num2};
        Process process = Runtime.getRuntime().exec(cmdArr);
        InputStream is = process.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String str = dis.readLine();
        process.waitFor();
        System.out.println(str);
    }
}

輸出:python

3

calculator_simple.py:shell

# coding=utf-8
from sys import argv

num1 = argv[1]
num2 = argv[2]
sum = int(num1) + int(num2)
print sum

顯然,在Java中經過Runtime調用Python程序與直接執行Python程序的效果是同樣的,能夠在Python中讀取傳遞的參數,也能夠在Java中讀取到Python的執行結果。須要注意的是,不能在Python中經過return語句返回結果,只能將返回值寫入到標準輸出流中,而後在Java中經過標準輸入流讀取Python的輸出值。編程

經過Jython調用

經過Jython調用Python?我在聽到這個概念的時候一臉懵逼,不是說好的在Java中調用Python程序嗎?這個Jython是什麼鬼?難道是一個在Java中調用Python程序的組件或工具?其實,關於Jython是什麼這個疑問,我估計有許多人在一開始接觸的時候也是很疑惑的,下面咱們就一一道來。架構

1. 什麼是Jython

Jython主頁:http://www.jython.org/currentdocs.html
按照官方的定義,Jython是Python語言在Java平臺的實現。這個概念彷佛有點拗口,反正我一開始並無理解。Python難道不已是一門語言了嗎?什麼叫作Jython是Python語言在Java平臺的實現?
實際上,之因此存在這樣的困惑主要是由於咱們對Python語言的相關概念掌握和理解不清楚致使的。
Python其實只是一個語言規範,它存在多個不一樣語言實現的版本。具體來講,目前Python語言存在以下幾個具體實現:
(1)CPython:CPython是標準Python,也是其餘Python編譯器的參考實現。一般提到「Python」一詞,都是指CPython。CPython由C編寫,將Python源碼編譯成CPython字節碼,由虛擬機解釋執行。沒有用到JIT等技術,垃圾回收方面採用的是引用計數。
(2)Jython:Jython是在JVM上實現的Python,由Java編寫。Jython將Python源碼編譯成JVM字節碼,由JVM執行對應的字節碼。所以能很好的與JVM集成,好比利用JVM的垃圾回收和JIT,直接導入並調用JVM上其餘語言編寫的庫和函數。
(3)IronPython:IronPython與Jython相似,所不一樣的是IronPython在CLR上實現的Python,即面向.NET平臺,由C#編寫。IronPython將源碼編譯成TODO CLR,一樣能很好的與.NET平臺集成。即與Jython相同,能夠利用.NET框架的JIT、垃圾回收等功能,能導入並調用.NET上其餘語言編寫的庫和函數。IronPython默認使用Unicode字符串。
(4)PyPy:這裏說的PyPy是指使用RPython實現,利用Tracing JIT技術實現的Python,而不是RPython工具鏈。PyPy能夠選擇多種垃圾回收方式,如標記清除、標記壓縮、分代等。
(5)Pyston:Pyston由Dropbox開發,使用C++11編寫,採用Method-at-a-time-JIT和Mark Sweep——Stop the World的GC技術。Pyston使用相似JavaScript V8那樣的多層編譯,其中也用到了LLVM來優化代碼。app

因此,咱們如今再來理解什麼是Jython就很是清楚了:Jython是Python語言規範在Java平臺的具體實現。具體來講,能夠將Python源碼編譯爲JVM能夠解釋執行的字節碼。
Jython本來叫作JPython,於1997年由Jim Hugunin建立,後來在1999年2.0版本發佈的時候由Barry Warsaw改名爲Jython,在這裏咱們就再也不深究爲何要把JPython改名爲Jython的緣由了。注意: Jython從2.0版本開始就與CPython的版本保持一致,即:Jython 2.7與CPython 2.7保持對應。框架

雖然咱們理解了什麼是Jython,可是還存在一個疑問,爲何Python語言存在這麼多不一樣語言的實現呢?爲何不能就只存在一個C語言實現的版本就能夠了呢?存在這麼多版本,真的給初學者帶來了許多困惑。
固然,要回答這個問題可能就須要深究一些歷史的緣由了,就此打住。咱們在此只討論使用Jython能作什麼以及如何使用Jython?eclipse

2. 使用Jython能作什麼

既然Jython是Python語言在Java平臺的實現,是Java語言實現的,那麼是否能夠在Jython程序中調用Java,在Java中也能調用Jython呢?
答案是確定的,實際上,Jython的主要通途就是在Java中調用Python程序;並且,還能夠直接在Jython程序中引用Java。函數式編程

3. 如何使用Jython

3.1 安裝Jython

在Jython的官方下載頁面咱們能夠看到以下描述(詳見:http://www.jython.org/downloads.html)

顯然,能夠下載2個Jython的jar包。其中,jython-installer-${version}.jar是用於安裝Jython的,jython-standalone-${version}.jar用於嵌入到Java程序中使用。
什麼意思?我一開始也是很疑惑,爲何要提供2個不一樣的jar包呢?他們有什麼不一樣呢?2個不一樣的Jar包如何使用呢?
首先,jython-installer-${version}.jar用於安裝Jython,就比如咱們須要安裝JRE,用於運行Java程序。除此以外,當須要在Python程序中引用一些公共的第三方庫時,也須要先安裝Jython才能下載所依賴的模塊。

下載jython-installer-${version}.jar完畢以後,進入控制檯,執行以下命令:

java -jar jython-installer-${version}.jar

此時會彈出一個圖形化的安裝界面,只須要一步一步選擇相應參數進行安裝便可。安裝完畢以後,請將Jython安裝目錄添加爲環境變量JYTHON_HOME,同時添加bin目錄到PATH變量中:PATH=$PATH:$JYTHON_HOME/bin
進入控制檯,執行以下命令就能夠進入Jython的交互環境,這與CPython(咱們一般說的Python)的命令行交互環境是同樣的。

> jython
Jython 2.7.0 (default:9987c746f838, Apr 29 2015, 02:25:11)
[Java HotSpot(TM) 64-Bit Server VM (Oracle Corporation)] on java1.8.0_121
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello,world")
hello,world
>>>

固然,咱們還可使用jython命令運行一個Python程序。

> jython helloworld.py
hello,world

helloworld.py:

import sys

print("hello,world")

上面咱們看到在Jython官網提供了2個Jar包,一個用於安裝Jython,執行Python程序。那麼,jython-standalone-${version}.jar又有什麼用途呢?
實際上,當咱們須要在Java中調用Python程序時,除了直接使用Java的Runtime調用,還能夠直接使用Jython的API進行調用,並且經過Jython API能夠直接調用Python程序中的指定函數或者對象方法,粒度更加精細。
當咱們須要調用Jython的API時有兩種方式:
第一,若是項目使用Maven進行構建,能夠直接添加Jython的依賴配置到pom.xml文件中,如:

<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython</artifactId>
    <version>2.7.0</version>
</dependency>

第二,能夠直接將jython-standalone-${version}.jar添加到項目classpath中,這樣也能夠調用Jython的相關API了。也就是說,jython-standalone-${version}.jar就是一個提供Jython API的jar獨立jar包。

3.2 Java調用Python程序實踐

Java經過Jython API調用Python程序,有幾種用法:
(1)在Java中執行Python語句,至關於在Java中嵌入了Python程序,這種用法不常見,也沒有太大的實際意義。

public static void main(String[] args) {
    System.setProperty("python.home", "D:\\jython2.7.0");
    PythonInterpreter interp = new PythonInterpreter();
    // 執行Python程序語句
    interp.exec("import sys");
    interp.set("a", new PyInteger(42));
    interp.exec("print a");
    interp.exec("x = 2+2");
    PyObject x = interp.get("x");
    System.out.println("x: " + x);
}

輸出:

42
x: 4

(2)在Java中簡單調用Python程序,不須要傳遞參數,也不須要獲取返回值。

public static void main(String[] args) throws IOException {
    System.setProperty("python.home", "D:\\jython2.7.0");
    String python = "D:\\simple_python.py";
    PythonInterpreter interp = new PythonInterpreter();
    interp.execfile(python);
    interp.cleanup();
    interp.close();
}

simple_python.py:

# coding=utf-8
print("Do simple thing in Python")
print("輸出中文")

(3)在Java中單向調用Python程序中的方法,須要傳遞參數,並接收返回值。Python既支持面向函數式編程,也支持面向對象編程。所以,調用Python程序中的方法也分別以面向函數式編程和麪向對象式編程進行說明。

public static void main(String[] args) throws IOException {
    System.setProperty("python.home", "D:\\jython2.7.0");
    
    // 1. Python面向函數式編程: 在Java中調用Python函數
    String pythonFunc = "D:\\calculator_func.py";
    
    PythonInterpreter pi1 = new PythonInterpreter();
    // 加載python程序
    pi1.execfile(pythonFunc);
    // 調用Python程序中的函數
    PyFunction pyf = pi1.get("power", PyFunction.class);
    PyObject dddRes = pyf.__call__(Py.newInteger(2), Py.newInteger(3));
    System.out.println(dddRes);
    pi1.cleanup();
    pi1.close();
    
    // 2. 面向對象式編程: 在Java中調用Python對象實例的方法
    String pythonClass = "D:\\calculator_clazz.py";
    // python對象名
    String pythonObjName = "cal";
    // python類名
    String pythonClazzName = "Calculator";
    PythonInterpreter pi2 = new PythonInterpreter();
    // 加載python程序
    pi2.execfile(pythonClass);
    // 實例化python對象
    pi2.exec(pythonObjName + "=" + pythonClazzName + "()");
    // 獲取實例化的python對象
    PyObject pyObj = pi2.get(pythonObjName);
    // 調用python對象方法,傳遞參數並接收返回值
    PyObject result = pyObj.invoke("power", new PyObject[] {Py.newInteger(2), Py.newInteger(3)}); 
    double power = Py.py2double(result);
    System.out.println(power);
    
    pi2.cleanup();
    pi2.close();
}

輸出:

8.0
8.0

calculator_func.py:

# coding=utf-8
import math

# 面向函數式編程
def power(x, y):
    return math.pow(x, y)

calculator_clazz.py:

# coding=utf-8
import math

# 面向對象編程
class Calculator(object):
    
    # 計算x的y次方
    def power(self, x, y):
        return math.pow(x,y)

(4)高級調用,也是在Java中調用Python程序最多見的用法:Python程序能夠實現Java接口,在Python中也能夠調用Java方法。

public static void main(String[] args) throws IOException {
    System.setProperty("python.home", "D:\\jython2.7.0");
    
    // Python程序路徑
    String python = "D:\\python\\fruit_controller.py";
    // Python實例對象名
    String pyObjName = "pyController";
    // Python類名
    String pyClazzName = "FruitController";
    
    Fruit apple = new Apple();
    Fruit orange = new Orange();
    
    PythonInterpreter interpreter = new PythonInterpreter();
    // 若是在Python程序中引用了第三方庫,須要將這些被引用的第三方庫所在路徑添加到系統環境變量中
    // 不然,在執行Python程序時將會報錯: ImportError: No module named xxx
    PySystemState sys = interpreter.getSystemState();
    sys.path.add("D:\\python");
    
    // 加載Python程序
    interpreter.execfile(python);
    // 實例 Python對象
    interpreter.exec(pyObjName + "=" + pyClazzName + "()");

    // 1.在Java中獲取Python對象,並將Python對象轉換爲Java對象
    // 爲何可以轉換? 由於Python類實現了Java接口,經過轉換後的Java對象只能調用接口中定義的方法
    GroovyController controller = (GroovyController) interpreter.get(pyObjName).__tojava__(GroovyController.class);
    controller.controllFruit(apple);
    controller.controllFruit(orange);
    
    // 2.在Java直接經過Python對象調用其方法
    // 既能夠調用實現的Java接口方法,也能夠調用Python類自定義的方法
    PyObject pyObject = interpreter.get(pyObjName);
    pyObject.invoke("controllFruit", Py.java2py(apple));
    pyObject.invoke("controllFruit", Py.java2py(orange));
    pyObject.invoke("printFruit", Py.java2py(apple));
    pyObject.invoke("printFruit", Py.java2py(orange));
    
    // 3.在Java中獲取Python類進行實例化對象: 沒有事先建立 Python對象
    PyObject pyClass = interpreter.get("FruitController");
    PyObject pyObj = pyClass.__call__();
    pyObj.invoke("controllFruit", Py.java2py(apple));
    pyObj.invoke("controllFruit", Py.java2py(orange));
    
    PyObject power = pyObj.invoke("power", new PyObject[] {Py.newInteger(2), Py.newInteger(3)});
    if(power != null) {
        double p = Py.py2double(power);
        System.out.println(p);
    }
    
    interpreter.cleanup();
    interpreter.close();
}

輸出:

Show: I am a java apple.
controllFruit Python Apple
controllFruit END
Show: I am a java orange.
controllFruit Python Orange
controllFruit END
Show: I am a java apple.
controllFruit Python Apple
controllFruit END
Show: I am a java orange.
controllFruit Python Orange
controllFruit END
Show: I am a java apple.
printFruit Python Apple
printFruit END
Show: I am a java orange.
printFruit Python Orange
printFruit END
Show: I am a java apple.
controllFruit Python Apple
controllFruit END
Show: I am a java orange.
controllFruit Python Orange
controllFruit END
8.0

fruit_controller.py:

# coding=utf-8

from calculator_clazz import Calculator
from java.lang import String
from org.test.inter import GroovyController
from org.test.inter import Fruit

# 在Python中實現Java接口: org.test.inter.GroovyController
class FruitController(GroovyController):
    
    # 實現接口方法
    def controllFruit(self, fruit):
        # 在Python中調用Java對象方法
        fruit.show()
        
        if(fruit.getType() == "apple"):
            print ("controllFruit Python Apple")
            
        if(fruit.getType() == "orange"):
            print ("controllFruit Python Orange")
        
        print ("controllFruit END")
    
    # 自定義新方法    
    def printFruit(self, fruit):
        fruit.show()
        
        if(fruit.getType() == "apple"):
            print ("printFruit Python Apple")
            
        if(fruit.getType() == "orange"):
            print ("printFruit Python Orange")
        
        print ("printFruit END")
    
    # 引用第三方python程序
    def power(self, x, y):
        cal = Calculator()
        return cal.power(x, y)

Java接口和實現類:

// 該接口用於在Python中實現
public interface GroovyController {
    public void controllFruit(Fruit fruit);
}

// 在Java中使用的接口
public interface Fruit {
    public String getName();
    public String getType();
    public void show();
}

// Apple
public class Apple implements Fruit {
    public String getName() {
        return "java apple";
    }
    
    public String getType() {
        return "apple";
    }

    public void show() {
        System.out.println("Show: I am a java apple.");
    }
}

// Orange
public class Orange implements Fruit {
    public String getName() {
        return "ava orange";
    }

    public String getType() {
        return "orange";
    }

    public void show() {
        System.out.println("Show: I am a java orange.");
    }
}

另外,對於在eclipse中運行時控制檯報錯:

Failed to install '': java.nio.charset.UnsupportedCharsetException: cp0

請添加VM參數:-Dpython.console.encoding=UTF-8,詳見:http://blog.csdn.net/xfei365/article/details/50955731

總結

雖然在Java中調用Python能夠有多種方式解決,甚至由於Jython的出現更顯得很是便利。可是這種程序間嵌套調用的方式不可取,首先拋開調用性能不說,增長了耦合複雜度。更加有效的方式應該是經過RCP或者RESTful接口進行解耦,這樣各司其職,也便於擴展,良好的架構是一個項目可以健康發展的基礎。在微服務架構大行其道的今天,這種程序間嵌套調用的方式將會逐漸被淘汰。

【參考】 http://tonl.iteye.com/blog/1918245 Java調用Python http://blog.csdn.net/supermig/article/details/24005585 Learning Python -- Java 經過JyThon調用Python實現的規則 http://blog.csdn.net/hpp1314520/article/details/72854011 java 利用Runtime.getRuntime().exec()調用python腳本並傳參 http://blog.csdn.net/xingjiarong/article/details/49424253 java調用python方法總結 https://zh.wikipedia.org/wiki/Jython Jython http://lib.csdn.net/article/python/1654 Jython的安裝及簡單例子 https://coolshell.cn/articles/2631.html 五大基於JVM的腳本語言 http://python.jobbole.com/82703/ 各類 Python 實現的簡單介紹與比較 https://www.oschina.net/translate/why-are-there-so-many-pythons 爲何有這麼多 Python?

相關文章
相關標籤/搜索