Java运行Python脚本

article/2025/10/30 5:43:14

    前段时间遇到了在JavaWeb项目中嵌入运行Python脚本的功能的需求。想到的方案有两种,一种是使用Java技术(Jython或Runtime.exec)运行Python脚本,另一种是搭建一个Python工程对外提供相应http或webservice接口。两种方案我都有实现,简单的测试了一下,本机环境两者的执行效率没有太大的差距。考虑到项目的情况,最终选择了第一种方案。
    闲话少说,我们赶紧看看Java怎么实现运行Python脚本的吧~

Jython实现

    简单来说,我们常说的Python是指CPython,由C语言编写。而这里说的Jython是由Java编写,在JVM中运行。显然,Jython天然就对Java有很高的亲和度(本身就是Java写的),能够利用JVM相关的技术,库和函数等资源。
    Jython从2.0版本开始就和Python的版本保持一致,目前最新的版本为2.7.2。Jython的官方下载网址:https://www.jython.org/download。网址中有两个下载包Jython Installer、Jython Standalone。Jython Installer类似一个应用程序(exe),双击安装即可在cmd中使用Jython;而Jython Standalone可以作为jar包直接引入到项目中使用。对于项目编程,直接引入Maven依赖即可。

<dependency><groupId>org.python</groupId><artifactId>jython-standalone</artifactId><version>2.7.1</version>
</dependency>

注意:引用的不是org.python.jython,这将会产生一堆疑难杂症。引入的是jython-standalone,这是专门给java调用jython的包,有较多的jar库支持。

Jython运行Python脚本

    Jython主要是使用PythonInterpreter类,调用方法大致有两种形式。

面向函数调用

    根据需要调用的方法名获取对应的PyFunction对象,调用__call__方法获取方法的返回值。
    在这种调用方式下,python脚本里面是定义了一些函数,且用Jython调用的时候,需要知道所要调用函数的名称。比如python脚本为:

# coding=utf-8
def main(str):print(str)

    那么java的关键代码如下:

PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec(script);
// python脚本中的方法名为main
PyFunction function = interpreter.get("main",PyFunction.class);
// 将参数代入并执行python脚本
PyObject o = function.__call__(Py.newString(params));

面向对象调用

    另外一种调用方式是,Python脚本是定义了一个类。可以通过类名获取对应的python对象,再用该对象调用类中定义的方法获取返回值。比如python脚本为:

# coding=utf-8
class Test(object):def main(self,str):print(str)

    那么java的关键代码如下:

PythonInterpreter interpreter = new PythonInterpreter();
interpreter.exec(script);
// 获取Python对象
PyObject pyObject = interpreter.get("Test");
// 调用方法,方法名为main。将参数代入并执行python脚本,获取返回值
// 传入参数对象的个数要与调用的方法一致
PyObject o= pyObj.invoke("main",new PyObject[]{Py.newString(params)})); 

引入第三方包

    Python作为最流行的开源编程语言之一,拥有广泛的第三方包,应该说当下的Python编程已无法离开这丰富的第三方包的支持。Jython本身也包含较多的jar包支持日常的编程,同时也支持引入第三方包。引入的方式如下两种,第一种是引入Jython本身提供的jar包:以引入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();

    第二种可以直接指定引入的第三方包所在的目录:

PythonInterpreter interpreter = new PythonInterpreter();
PySystemState sys = interpreter.getSystemState();
sys.path.add("C:/jython2.7.1/Lib");

    第二种方法有另一种写法:

PythonInterpreter interpreter = new PythonInterpreter();
// 下面是加入jython的库,需要指定jar包所在的路径。如果有自己写的包或者第三方,可以继续追加
interpreter.exec("import sys");
interpreter.exec("sys.path.append('C:/jython2.7.1/Lib')");
interpreter.exec("sys.path.append('C:/jython2.7.1/Lib/site-packages')");

Jython实现优缺点

    优点:本身就是Java编写的,使用起来比较方便,且相比后面的Runtime.exec的路子相对比较简单。
    缺点:现在最流行的还是Python(即CPython),不管是社区活跃程度还是更新迭代速度都是Jython望尘莫及的,这也意味着Python许多强大的第三方包是无法即时更新到Jython库中,即便Jython提供引入第三方包的功能,但是总会有莫名其妙的问题,总是莫名的提示导包出错。编程的时候有不少时间浪费在处理导包问题,很影响使用的心情。
    而最致命的问题是,Jython竟然不支持中文!!!只要Python脚本中包含中文,就会一直报错。我在网上找了很多资料,也尝试去在代码中转码,还是无法解决,也没有找到解决方法。由于我在使用的时候还是使用2.7.1版本,不清楚2.7.2版本有没有解决这个问题。
    如果有朋友解决了这个问题,欢迎分享一下解决方式,谢谢~~
    总之在项目中,我是彻底放弃使用Jython了。

Runtime.exec实现

    Jdk提供Runtime类用于执行JVM外的程序,其效果类似于调用命令行执行指令。在安装了Python的前提下,我们可以模拟命令行窗口执行Python的脚本。如该Runtime类除了可以执行Python代码,也可执行Java、shell代码等,具有非常强大的功能。但需要注意的是,Runtime类并不等同于命令行。
    在命令行中,我们可以逐行执行脚本代码:
在这里插入图片描述
    然而使用Runtime的时候,我发现并不能每行代码像命令行那样逐行用exec执行,猜测是每次exec后返回的结果之间没有联系。
    如果要使用Runtime.exec执行Python脚本,就要模拟命令行的命令:python py文件的路径 参数...

    该命令的第一个参数为字符串“python”,指定命令的执行程序为python。这个需要在系统中安装python程序。有些系统可能同时安装了python2和python3,这个需要先在命令行中执行一次python看调用的是哪个。一般默认python调用python2,python3调用python3。
    第二个参数是需要执行的Python脚本文件的绝对路径。也就是说Python脚本文件在执行的过程中需要存放在本地。
    第三个参数是参数列表,如果脚本中需要传入参数,那么这里需要根据形参的顺序依次传入对应的实参。

    比方说,如果需要执行的python脚本名字为Hello.py,存放在E盘下,那么其绝对路径为E:/Hello.py,该脚本需要传入一个字符串参数,假如为“world”,那么在cmd执行的命令为:python E:/Hello.py "world"
    在Java中可以用一个字符串数组存储上面的命令:

String command = "E:/Hello.py";
String params = "world";
String[] cmd = new String[]{"python",command,params};
Process process = Runtime.getRuntime().exec(cmd);

这里有个坑,当params是一个包含空格的字符串时,脚本执行结果会有问题。
比如String params=“this is a brand new day”。那么实际模拟cmd执行的命令是这样的:python E:/Hello.py this is a brand new day
看出问题了吗?我们理想认为参数是一个(this is a brand new day),但实际上却变成了6个,第一个是this,第二个是is,依次类推。由于脚本只需要一个参数,所以只会把第一个参数(this)传入到脚本中执行!
解决方法很简单:只要在params字符串前后加上双括号即可!String params="\"this is a brand new day\""

    在获取到Process对象后,通过getInputStream()获取子进程标准输入流、getErrorStream()获取子进程错误流,从而得到脚本的执行结果、错误反馈信息。并使用waitFor()使当前线程等待至子进程结束。但如果在同一个线程中,主进程和子进程有可能会出现相互等待对方结束而出现死锁的情况。这时候需要新开一个线程将获取标准输入流或错误流分开。完整的关键代码如下:

/* 注意:cmd的格式:“python py文件的路径 参数...”*  注意2:参数是字符串的时候,有可能会出现参数只解析第一个词的情况,此时必须在整个字符串参数首尾手动添加双引号(单引号都不行)*  则下面的cmd=python E:/test/pythontest/Demo.py “params”*/
//String cmd = String.format("python %s \"%s\"",command,params);
// 也可以用String[],但是params传入前也需要手动在字符串前后加双引号
params = "\"" + params + "\"";
String[] cmd = new String[]{"python",command,params};
Process process = Runtime.getRuntime().exec(cmd);// error的要单独开一个线程处理。其实最好分成两个子线程分别处理标准输出流和错误输出流
ProcessStream stderr = new ProcessStream(process.getErrorStream(), "ERROR", charset);
stderr.start();
// 获取标准输出流的内容
BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream(), charset));
while ((line = stdout.readLine()) != null) {rtnSb.append(line).append("\r\n");
}
rtnMap.put("result",rtnSb.toString());
rtnMap.put("error",stderr.getContent());
//关闭流
stdout.close();
int status = process.waitFor();
if (status != 0) {System.out.println("return value:"+status);
}
process.destroy();

    到此,两种方法均已介绍完毕。附上完整运行工具类和相应的进程工具类:

import org.python.core.Py;
import org.python.core.PyFunction;
import org.python.core.PyObject;
import org.python.core.PySystemState;
import org.python.util.PythonInterpreter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;/*** java执行python的工具类*  使用条件,python代码中有一个main方法,其他代码都在main方法中执行* @author hjw*/public class RunPython {private static Logger logger = LoggerFactory.getLogger(RunPython.class);/*** 使用jython运行py代码,缺点:一旦引用第三方库容易报错,而即便手动设置第三方库,也有可能出现错误* @param script python解析代码* @param params python代码中的参数* @return*/public static Map<String,Object> runPythonByJython(String script, String params){Map<String,Object> rtnMap = new HashMap<>();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();
//        // 下面是加入jython的库,需要指定jar包所在的路径。如果有自己写的包或者第三方,可以继续追加
//        interpreter.exec("import sys");
//        interpreter.exec("sys.path.append('C:/jython2.7.1/Lib')");
//        interpreter.exec("sys.path.append('C:/jython2.7.1/Lib/site-packages')");try {interpreter.exec(script);// 假设python有一个main方法,包含所有实现需求的代码。换言之,传来的python代码只需要执行main方法就能完成需求PyFunction function = interpreter.get("main",PyFunction.class);// 将报文代入并执行python进行解析PyObject o = function.__call__(Py.newString(params));rtnMap.put("result",o);interpreter.cleanup();interpreter.close();} catch (Exception e) {e.printStackTrace();rtnMap.put("error",e);}return rtnMap;}/*** 使用Runtime.getRuntime().exec()解析运行python* @param command 解析的python代码,即py文件的路径* @param params python代码中的参数* @param charset 码表* @return*/public static Map<String,Object> runPythonByRuntime(String command, String params, String charset) {Map<String,Object> rtnMap = new HashMap<>();String line;StringBuffer rtnSb = new StringBuffer();try {/* 注意:cmd的格式:“python py文件的路径 参数...”*  注意2:参数是字符串的时候,必须在首尾手动添加双引号(单引号都不行)*  则下面的cmd=python E:/test/pythontest/Demo.py “params” */
//            String cmd = String.format("python %s \"%s\"",command,params);// 也可以用String[],但是params传入前也需要手动在字符串前后加双引号String[] cmd = new String[]{"python",command,params};Process process = Runtime.getRuntime().exec(cmd);// error的要单独开一个线程处理。其实最好分成两个子线程处理标准输出流和错误输出流ProcessStream stderr = new ProcessStream(process.getErrorStream(), "ERROR", charset);stderr.start();// 获取标准输出流的内容BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream(), charset));while ((line = stdout.readLine()) != null) {rtnSb.append(line).append("\r\n");}rtnMap.put("result",rtnSb.toString());rtnMap.put("error",stderr.getContent());//关闭流stdout.close();int status = process.waitFor();if (status != 0) {System.out.println("return value:"+status);logger.info("event:{}", "RunExeForWindows",process.exitValue());}process.destroy();} catch (Exception e) {e.printStackTrace();}return rtnMap;}
}

线程工具类:

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;/*** java执行python的线程工具类*/
public class ProcessStream extends Thread {private static Logger logger = LoggerFactory.getLogger(ProcessStream.class);private InputStream inputStream;private String streamType;private StringBuffer buf;private String charset;private volatile boolean isStopped = false; // 用于判断本线程是否执行完毕,用volatile保证线程安全public ProcessStream(InputStream inputStream, String streamType, String charset) {this.inputStream = inputStream;this.streamType = streamType;this.buf = new StringBuffer();this.charset = charset;this.isStopped = false;}@Overridepublic void run() {try {// 默认编码为UTF-8,如果有传入编码,则按外部编码String exactCharset = StringUtils.isBlank(charset) ? "UTF-8" : charset;BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, exactCharset));String line = null;while ((line = bufferedReader.readLine()) != null) {this.buf.append(line + "\n");}bufferedReader.close();} catch (IOException e) {logger.error("Failed to successfully consume and display the input stream of type " + streamType + ".", e);} finally {this.isStopped = true;synchronized (this) {notify();}}}/*** 当主线程调用本方法获取本子线程的输出时,若本子线程还没执行完毕,主线程阻塞到子线程完成后再继续执行*/public String getContent() {if (!this.isStopped) {synchronized (this) {try {wait();} catch (InterruptedException ignore) {ignore.printStackTrace();}}}return this.buf.toString();}
}

http://chatgpt.dhexx.cn/article/M25ZqbPy.shtml

相关文章

Java项目分层

MVC模式 在实际的开发中有一种项目的程序组织架构方案叫做MVC模式&#xff0c;按照程序 的功能将他们分成三个层&#xff0c;如下图&#xff1a;Modle层&#xff08;模型层&#xff09;、View层&#xff08;显示层&#xff09;、Controller层&#xff08;控制层&#xff09;。…

java项目收获总结_java开发项目收获心得

1 java开发项目收获心得 it行业现在的发展如日中天,很多人都纷纷走进这个行业,而java作为跨平台的编程语言更是受欢迎。java其实相对其他语言来说的确很有优势,但是也有点缺陷,但是以后发展到什么程度,谁都不知道。那么下面小编给大家说说java开发项目收获心得,希望能对你…

java查看jar包依赖_java项目开发中如何查找到项目依赖的jar包?

不管是java普通工程,还是java web项目,甚至是android项目,依赖包的管理有2种: 1.直接依赖jar包 这种方式简单直白,项目下载后在正确的ide或者稍微做转换就可以运行起来。比如java web工程的WEB-INF/lib下 只要按这个步骤Java Build Path=>Add Libraty=>Web App Libr…

Java小白必看:开发一个编程项目的完整流程(附100套Java编程项目源码+视频)

我相信很多Java新手都会遇到这样一个问题&#xff1a;跟着教材敲代码&#xff0c;很容易&#xff1b;但是让他完整的实现一个应用项目&#xff0c;却不会&#xff1b;不知道从哪里开始&#xff0c;不知道实现一个项目的完整流程是怎样的&#xff0c;看似很简单的一个问题&#…

分享67套基于Java开发的Java毕业设计实战项目(含源码+毕业论文)【新星计划】

【新星计划】分享67套基于Java开发的Java毕业设计实战项目(含源码毕业论文) 基于Java开发的Java毕业设计实战项目 本文中的所有主题都来自互联网。如果您侵犯您的权利&#xff0c;请及时联系Blogger&#xff0c;博主将及时处理。 投诉邮箱&#xff1a;1919101926qq.com (没事…

分享一些我的学习方法

赖勇浩&#xff08;http://laiyonghao.com &#xff09; 经常听到和看到一些前辈提起搞编程这一行最大的痛苦在于知识的更新太频繁&#xff0c;如同逆水行舟&#xff0c;不进则退&#xff0c;稍一松懈&#xff0c;就跟不上潮流。的确如此&#xff0c;既然身在 IT 界&#xff0c…

规划之路:SLAM学习经验分享

针对想学SLAM的提问&#xff0c;我觉得我还是有一定的发言权。 作为一个刚入坑SLAM一年多的初学者&#xff0c;首先想说的就是这个研究方向比较广&#xff0c;大方向按搭载传感器分为激光SLAM和视觉SLAM两种&#xff0c;激光SLAM搭载激光雷达&#xff0c;视觉SLAM搭载单目、双目…

学习经验分享之十三:如何撰写论文

一、文献积累 顶刊or顶会&#xff1a; AAAI: American Association for AI National Conference CVPR: IEEE Conf on Comp Vision and Pattern Recognition IJCAI: Intl Joint Conf on AI ICCV: Intl Conf on Computer Vision ICML: Intl Conf on Machine Learning KDD: Knowl…

学习经验分享【25】记录个人的一些学习体会(更新TensorBoard)

本博文将记录和分享个人的一些学习工作体会和踩过的坑&#xff0c;希望对大家能有帮助。 一、对比实验 用yolov5l.ymal和yolov5n.ymal文件&#xff0c;然后加载yolov5s.pt后&#xff0c;训练得到的模型效果极差&#xff08;mAP差30%&#xff09;&#xff0c;重新训练后换为yol…

个人学习方法分享

hi&#xff0c;大家好&#xff0c;今天来分享一下&#xff0c;我的一些学习经验&#xff0c;希望对大家学习有帮助。 我的学习方法 学习计划 核心思想&#xff1a;把自己像产品一样打造&#xff0c;就像张一鸣经典语录&#xff1a;run company as a product 制定目标 首先学习需…

分享自己学英语的经验

其实学好英语对于一个人真的很有用&#xff0c;尤其是对工程师来说&#xff0c;各种文献资料&#xff0c;代码&#xff0c;最新的技术文章&#xff0c;太多是英文的了&#xff0c;一个典型的例子就是&#xff0c;作为一个程序员和外国客户做过培训&#xff0c;当其他同事英语没…

【学习经验分享NO.21】学习资料分享(持续更新)

本博客将收集整理人工智能深度学习相关资料&#xff0c;进行整理&#xff0c;供大家学习使用。如果有需要帮忙整理的请留言。将不断更新&#xff0c;请持续关注。 一、深度学习论文资料 链接&#xff1a;https://pan.baidu.com/s/18LO5df0dp9-IE8Z3aFyrPg 提取码&#xff1a;c…

学习经验分享之十:Pycharm

前 言&#xff1a;作Pycharm有社区版和正式版&#xff0c;初学深度学习算法的小伙伴可能会选择社区版&#xff0c;因为正式版只有30天试用期&#xff0c;但是在实际使用过程中&#xff0c;社区版会出现很多功能上的不足&#xff0c;比如无法网上连接服务器&#xff0c;以及其他…

个人学习经验

这是一个ubuntu上学习cv遇到的一些常见问题的帮助和解决方法。 有些东西我会慢慢往上面加 文章目录 创建新环境conda添加镜像源安装模块在jupyter中使用新环境在pycharm中使用新环境 使用scp命令传输文件使用mobaxterm连接服务器 转数据集标注格式anaconda上安装好用的插件使用…

学习经验分享之十一:硕士论文撰写心得

硕士论文由于需要学校提交教育部盲审&#xff0c;导致通过率并不能达到百分百。翟天临事件之后&#xff0c;盲评通过率明显变低一般按照学科分布来说&#xff0c;盲审通过率最低的是工学&#xff0c;通过了为69.9%&#xff0c;其次为哲学&#xff0c;通过率为73.8%&#xff0c;…

我的 C# 学习经验分享

.NET 20 周年的学习挑战赛刚结束了第一阶段&#xff0c; 不知道各位小伙伴参加了没有 &#xff1f; 有人问现在学习 C# 是不是有点过时 &#xff1f; 也有人问现在 C# 能做什么 &#xff1f;更有人问学习 C# 能否找到工作 &#xff1f; 或者你从不同的专家 &#xff0c;不同的从…

分享学习语言的经验

在人类所制造的工具中&#xff0c;有的可以将人送上月球&#xff0c;有的可以驾驭原子的火焰&#xff0c;但是只有计算机软件才能透彻的返照出人类的心智。 --《Java与模式》 软件能够返照出人类的心智&#xff0c;那么计算机语言就是我们展现心智的基石。经常在一些论坛上和贴…

分享一些自己的学习经验和技巧

毕业两年多&#xff0c;工作三年多&#xff0c;技术水平也不是什么出类拔萃&#xff0c;万里挑一的地步&#xff0c;从最初的Java工程师&#xff0c;到产品&#xff0c;再到大数据&#xff0c;到最后的运维工程师&#xff0c;研发工程师&#xff0c;短短三年多&#xff0c;总是…

爬虫学习经验分享-------某点评网站

某点评网站爬虫分享----0基础学习&#xff0c;解决反爬&#xff0c;加密&#xff0c;ip代理&#xff0c;封ip“403 Forbidden”等问题。 最近有个调查的项目&#xff0c;其中包含了对于网络数据的爬取。而之前我并没学过网络爬虫&#xff0c;因此我也抱着学习的心态来学习如何爬…

前端学习总结,经验分享,项目经验分享过程

前言 来&#xff0c;和魔王哪吒总结一下&#xff0c;分享一下自己对前端学习总结&#xff0c;经验分享&#xff0c;以及写过的项目经验分享过程。 如果觉得还不错的话&#xff0c;浏览的过程中&#xff0c;需要您&#xff1a; 点赞&#xff0c;分享&#xff0c;评论有钱的捧…