在微服务架构大行其道的今天,对于将程序进行嵌套调用的作法其实并不可取,甚至显得有些愚蠢。固然,之因此要面对这个问题,或许是由于一些历史缘由,或者仅仅是为了简单。刚好我在项目中就遇到了这个问题,须要在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?