在Java中调用Python

写在前面

在微服务架构大行其道的今天,对于将程序进行嵌套调用的做法其实并不可取,甚至显得有些愚蠢。当然,之所以要面对这个问题,或许是因为一些历史原因,或者仅仅是为了简单。恰好我在项目中就遇到了这个问题,需要在Java程序中调用Python程序。关于在Java中调用Python程序的实现,根据不同的用途可以使用多种不同的方法,在这里就将在Java中调用Python程序的方式做一个总结。

直接通过Runtime进行调用

我们知道,在Java中如果需要调用第三方程序,可以直接通过Runtime实现,这也是最直接最粗暴的做法。

复制代码
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);
	}
}

输出:

3

calculator_simple.py:

复制代码
# 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来优化代码。

所以,我们现在再来理解什么是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?

  1. 使用Jython能做什么

既然Jython是Python语言在Java平台的实现,是Java语言实现的,那么是否可以在Jython程序中调用Java,在Java中也能调用Jython呢?

答案是肯定的,实际上,Jython的主要通途就是在Java中调用Python程序;而且,还可以直接在Jython程序中引用Java。

  1. 如何使用Jython

3.1 安装Jython

在Jython的官方下载页面我们可以看到如下描述(详见:http://www.jython.org/downloads.html)![](https://i-blog.csdnimg.cn/direct/820de61e01fe47d1b0a5ac1dcf440ec1.png)

显然,可以下载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)的命令行交互环境是一样的。

复制代码
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的出现更显得非常便利。但是这种程序间嵌套调用的方式不可取,首先抛开调用性能不说,增加了耦合复杂度。更加有效的方式应该是通过RPC或者RESTful接口进行解耦,这样各司其职,也便于扩展,良好的架构是一个项目能够健康发展的基础。在微服务架构大行其道的今天,这种程序间嵌套调用的方式将会逐渐被淘汰。

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json