前言
Python和Java都是当前非常热门的编程语言,它们各自有自己的优缺点。在有些场景下,我们可能需要使它们互相调用,以满足业务或者快速部署/整合的需求。关于Python和Java的相互调用,网上也有很多的博客,但都是零零散散,这里我在实践的同时,也顺便把踩到的坑都记录一下,最后会提供一个完整的Demo的地址。
这个Demo的调用过程是这样的:Java代码先调用Python,在Python中,又调用了Java代码,这就相当于整个Java代码执行过程中,有一部分是由Python执行的。
这个Demo主要使用的是Jython这个库。关于什么是Jython,有什么优缺点,可以参考这篇文章。
前置条件:
- 已安装JDK
- 已安装Python
- 已安装Eclipse
Eclipse安装并配置PyDev
之前学习Python,一直使用的是PyCharm IDE,虽然该IDE也很好用,但在学习Java和Python互相调用的时候,还得回到Eclipse中来,因此了解到Eclipse也有Python相关的插件,叫PyDev。
Eclipse中安装PyDev的具体过程可以参考这篇文章,安装后,python文件会被自动关联。和Eclipse提供的Java Editor一样,用PyDev提供的Python editor编辑python文件时,也会有各种提示,这极大的加快了开发速度,非常好用。值得一提的是,在安装好PyDev之后,还需要为其配置Python的解释器:Window -> Preferences -> PyDev -> Interpreters -> Python Interpreter,PyDev会自动寻找已安装的Python路径,通常点击Quick Auto Config即可(如果不能自动找到已安装的Python,需要点击New来手动指定Python.exe的路径):
现在发车
我是用maven来管理依赖,先看下整个Demo项目的结构:
按照上面的描述,这里主测试类是CallPython.java,它会调用calljava.py,而calljava.py又会调用HelloWorld.java中的sayHello()方法,整个过程都有相应的输出。
其中,pom.xml中自然会引入Jython的包:
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.5.3</version>
</dependency>
接下来是CallPython.java,主要逻辑是创建了一个Jython库中的PythonInterpreter实例,然后调用指定的脚本,并传入了一个参数,但要注意:
- 这里通过PythonInterpreter类向Python环境中设置了一个参数name,其值为World
- 这里指定的python文件路径,是基于项目根目录的,这里的路径为src/calljava.py
package com.my.py;
import java.util.Properties;
import org.python.core.PySystemState;
import org.python.util.PythonInterpreter;
public class CallPython {
public static void main(String[] args) {
//Set local variables for Python
Properties properties = new Properties();
properties.put("name", "World");
System.out.println("1.In Java--------Start calling Python script file");
//Call Python script
executePythonScript("src/calljava.py", properties);
System.out.println("6.In Java--------End Pythion call");
}
public static void executePythonScript(String scriptFile, Properties properties) {
PythonInterpreter interpreter = getPythonInterpreter(properties);
try {
interpreter.execfile(scriptFile);
} catch (Exception e) {
System.out.println("Execute Python encounter exception:" + e);
}
}
private static PythonInterpreter getPythonInterpreter(Properties properties) {
PySystemState.initialize(System.getProperties(), properties, new String[0]);
PythonInterpreter interpreter = new PythonInterpreter();
for (Object key : properties.keySet()) {
interpreter.set((String)key, properties.get(key));
}
return interpreter;
}
}
以下是calljava.py,里面通过from java.packages来导入了要调用的类,然后直接创建该类的实例,并使用实例调用方法。注意,这里可以直接使用在Java环境中设置来的参数name:
from com.my.py import HelloWorld
#The [name] variable is set in Java through PythonInterpreter
print("2.In Python------Parameter [name] is set to [%s]" % name)
hello = HelloWorld()
print("3.In Python------Now calling Java")
hello.sayHello(name)
print("5.In Python------Exit Python environment")
最后,是这个被python调用的Java类:
package com.my.py;
public class HelloWorld {
public void sayHello(String name) {
System.out.println("4.In Java--------HelloWorld.sayHello() -->> Hello " + name);
}
}
很简单,只是在sayHello()方法中打印了一下参数而已。到这里,这个Demo的代码就算是完成了。
结果分析
最后执行主测试类CallPython.java来看下执行结果:
从结果中可以看到,输出结果和预期一致:
- 第1行由CallPython.java输出
- 第2行由calljava.py输出,这说明Java已经调用到Python代码了,并且参数name已经传入
- 第3行由calljava.py输出,将用传入的name参数调用HelloWorld类的sayHello()方法
- 第4行由HelloWorld.java输出的,说明sayHello()方法已被成功调用,输出了name参数
- 第5行由calljava.py输出,说明调用Java代码已结束,回到了Python环境中
- 第6行由CallPython.java输出,说明调用Python结束,回到了Java环境中
整个过程看似很顺利,实际则是在踩过各种坑之后的。接下来说说本次实践踩过的坑。
踩过的坑
以下内容只是说明我在实践中遇到了哪些问题,可能不是正确的方法,仅作参考。
1. 其实最开始我是参照这篇文章写的,但是在依赖的Jython包的时候,我在mvnrepository中看到最新的版本为2.7.0,于是毫不犹豫的使用了最新稳定版本,结果执行的时候遇到了以下错误:
说“不支持的编码异常”,通过这篇文章得知,解决办法是需要在Run Configuration的VM参数中加入以下内容:
-Dpython.console.encoding=UTF-8
2. 可是我的代码中没有中文哪,怎么会这样报编码异常呢?抱着试试的态度,我增加了VM参数,再执行,结果是:
仔细一看第一句,是说不能导入site模块,然而我压根儿不知道为什么会引入这个site模块。而且最后一句还给出了提示:通过指定python.import.site=false来避免。后来在这篇文章中看到解决办法,通过以下代码来初始化PythonInterpreter(主要是加入了这个python.import.site的配置):
Properties props = new Properties();
props.put("python.import.site", "false");
Properties preprops = System.getProperties();
PythonInterpreter.initialize(preprops, props, new String[0]);
PythonInterpreter interpreter = new PythonInterpreter();
3. 之后,果然不报site模块的错了,但是又遇到以下问题:
搜索一番之后,在这篇文章中看到解决办法:把依赖的版本退回到2.5.2即可解决。但是我在mvnrepository中没看到2.5.2版本,只看到有2.5.3,于是才有了本文一开始的pom.xml中,直接引入了Jython的2.5.3版本。
4.降低依赖的Jython版本后,再执行,遇到以下问题:
1.In Java--------Start calling Python script file
Execute Python encounter exception:IOError: (2, 'File not found - F:\\EclipseWorkspace\\JavaPythonDemo\\calljava.py')
6.In Java--------End Pythion call
好吧,一看就知道,没找到要执行的Python文件,修改CallPython.java调用时的参数为src/calljava.py,再次执行,成功!
6.最后,检查第1步增加的VM参数,以及第2步增加的python.import.site参数,发现它们都是多余的,于是删除,所以得到了上述代码。
总结
在学习新知识的时候,肯定会遇到很多问题。幸运的是,这些同样的问题在我之前肯定都有人遇到过,并且也会有相应的解决办法,站在巨人的肩膀上,肯定会快很多。本次实践,虽然花了很多时间去解决问题,但是当看到执行结果是成功的时候,那种心情还是很激动的。
Demo下载:
https://github.com/wanxiaolong/JavaPythonDemo
参考地址:
http://blog.csdn.net/wxiaow9000/article/details/51660299
http://blog.csdn.net/xfei365/article/details/50955731
http://blog.csdn.net/fei33423/article/details/53491414
http://blog.csdn.net/n1007530194/article/details/46972303