在微服務架構大行其道的今天,對於將程序進行嵌套調用的作法其實並不可取,甚至顯得有些愚蠢。固然,之因此要面對這個問題,或許是由於一些歷史緣由,或者僅僅是爲了簡單。剛好我在項目中就遇到了這個問題,須要在Java程序中調用Python程序。關於在Java中調用Python程序的實現,根據不一樣的用途可使用多種不一樣的方法,在這裏就將在Java中調用Python程序的方式作一個總結。html
咱們知道,在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調用Python?我在聽到這個概念的時候一臉懵逼,不是說好的在Java中調用Python程序嗎?這個Jython是什麼鬼?難道是一個在Java中調用Python程序的組件或工具?其實,關於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
既然Jython是Python語言在Java平臺的實現,是Java語言實現的,那麼是否能夠在Jython程序中調用Java,在Java中也能調用Jython呢?
答案是確定的,實際上,Jython的主要通途就是在Java中調用Python程序;並且,還能夠直接在Jython程序中引用Java。函數式編程
在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包。
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?