通过Java运行Python脚本,只需导入一次

huangapple go评论78阅读模式
英文:

Run Python script through Java with importing just once

问题

Update April 2020: 由于下面的回答提出了一个非常好且简单的解决方案,我已接受该答案,但是我从未使我的代码正常工作!如果有人已经构建了类似的东西,请与我联系!


我有一个简单的脚本 my-script.py,使用特定的 Python 库进行非常快速的计算:

import sys
import special-library

def fun(x):
  进行一些操作

fun(sys.argv[1])

在我的 Java 代码中,我在代码的不同部分经常使用/调用这个脚本,使用 ProcessBuilder 方法,这意味着我只需运行命令 python my-script.py argument
这意味着每次调用这个脚本,我都必须执行 import 命令,这是这个过程中最耗时的部分(实际上计算部分更快)。

有没有解决方案让我只调用一次导入?我稍微了解了一下 Jython - 是否可能编写一个类或类似的东西,它会被初始化并且只进行一次导入,然后每次我想执行计算部分(fun)时调用它的一个函数?是否有人做过类似的事情,或者有其他解决方案?

第一次尝试实现

我尝试编写了一个名为 'pipe' 的 Java 类,它将执行 Python 脚本一次。现在脚本连续从 stdin 读取,当它得到一个输入 argument 时,它执行计算并返回结果:

import sys
import special-library

def fun(x):
  进行一些操作
  print('END')

for arg in sys.stdin:
  fun(arg)

当然,当我从命令行测试并提供输入参数时,这是有效的。

Java 类如下:

package my.package;
import java.io.*;

public class PythonScriptExecuter {

	private static BufferedReader pythonToJavaReader;
	private static PrintWriter javaToPythonWriter;

	public PythonScriptExecuter() throws Exception {
		String MPBNScriptFile = "fullPathToScriptFile";
		ProcessBuilder pb = new ProcessBuilder("python", MPBNScriptFile);
		pb.redirectErrorStream(true);

		// 启动脚本
		Process p = pb.start();

		pythonToJavaReader = getOutputReader(p); // 从 Python 到 Java
		javaToPythonWriter = getInputWriter(p); // 从 Java 到 Python
	}

	// Python => Java
	private static BufferedReader getOutputReader(Process p) {
		return new BufferedReader(new InputStreamReader(p.getInputStream()));
	}

	// Java => Python
	private static PrintWriter getInputWriter(Process p) {
		return new PrintWriter(new OutputStreamWriter(p.getOutputStream()));
	}

	public static ArrayList<String> getResults(String argument) throws IOException {
		// 将参数发送到 Python 脚本
		javaToPythonWriter.println(argument);
		//javaToPythonWriter.flush();

		// 逐个获取结果
		ArrayList<String> results = new ArrayList<>();

		int count = 0;
		String line;
		while (!(line = pythonToJavaReader.readLine()).equals("END")) {
            // 处理 `line` 字符串 
			results.add(line);
		}

		return results;
	}
}

因此,在我的代码的初始化部分的某个地方,我有这个简单的行来启动进程:

new MPBNPythonScriptExecuter();

然后,当我想要获得一些结果时,我使用:

String arg = "something";
ArrayList<String> res = PythonScriptExecuter.getResults(arg);

整个问题似乎出在从脚本读取输出的那一行:

while (!(line = pythonToJavaReader.readLine()).equals("END"))
英文:

Update April 2020: I have accepted the answer below because it proposes a very good and simple solution to my problem but I never got my code to work! If anyone has already built something similar please contact me!


I have a simple script my-script.py that does some calculations very fast using a specific library written in Python:

import sys
import special-library

def fun(x):
  do something

fun(sys.argv[1])

In my Java code, I use/call this script a lot in different part of the code, using the ProcessBuilder approach, which means that I simply run the command python my-script.py argument.
This means that every time I call this script, I have to execute the import commands which is the most time-consuming thing in this (the calculation part is actually faster).

Is there a solution so that I call the import just once? I looked a little bit about Jython - would be possible to write a class or something that would be initiated and do the import once and then calling a function of it every time I want to do the calculations part (fun)? Has anyone done something similar or have any other solution to this?

First Attempt at Implementation

I've tried to write a 'pipe' Java Class that will execute the python script once. Now the script reads continuously from the stdin and when it gets an input argument, it does the calculations and returns the result:

import sys
import special-library

def fun(x):
  do something
  print(&#39;END&#39;)

for arg in sys.stdin:
  fun(arg)

And this works of course when I test it from the command line and supplying it with input arguments.

The Java Class is as follows:

package my.package;
import java.io.*;

public class PythonScriptExecuter {

	private static BufferedReader pythonToJavaReader;
	private static PrintWriter javaToPythonWriter;

	public PythonScriptExecuter() throws Exception {
		String MPBNScriptFile = &quot;fullPathToScriptFile&quot;;
		ProcessBuilder pb = new ProcessBuilder(&quot;python&quot;, MPBNScriptFile);
		pb.redirectErrorStream(true);

		// Start script
		Process p = pb.start();

		pythonToJavaReader = getOutputReader(p); // from python to java
		javaToPythonWriter = getInputWriter(p); // from java to python
	}

	 // Python =&gt; Java
	private static BufferedReader getOutputReader(Process p) {
		return new BufferedReader(new InputStreamReader(p.getInputStream()));
	}

	// Java =&gt; Python
	private static PrintWriter getInputWriter(Process p) {
		return new PrintWriter(new OutputStreamWriter(p.getOutputStream()));
	}

	public static Arraylist&lt;String&gt; getResults(String argument) throws IOException {
		// send argument to python script
		javaToPythonWriter.println(argument);
		//javaToPythonWriter.flush();

		// get results back one by one
		ArrayList&lt;String&gt; results = new ArrayList&lt;&gt;();

		int count = 0;
		String line;
		while (!(line = pythonToJavaReader.readLine()).equals(&quot;END&quot;)) {
            // process `line` string 
			results.add(line);
		}

		return results;
	}
}

So, what I do is that somewhere in the initialization part of my code I have this simple line to start the process:

new MPBNPythonScriptExecuter();

and later when I want to get some results back, I use:

String arg = &quot;something&quot;
Arraylist&lt;String&gt; res = PythonScriptExecuter.getResults(arg);

and the whole thing hangs on the reading-the-output-from-the-script line:

while (!(line = pythonToJavaReader.readLine()).equals(&quot;END&quot;))

Any ideas what is going wrong here?

答案1

得分: 7

你可以使用管道在Java和Python之间进行通信。

在Python中,您可以像现在一样运行Python脚本(不使用命令行参数):

Python脚本:

您可以在Python中编写一个无限循环,该循环将执行以下操作:

  1. 从标准输入读取数据(这将是您传递给函数的参数)。
  2. 调用您的函数。
  3. 将答案写入标准输出。

在Java中,您可以执行以下步骤:

  1. 编写一个方法以将参数发送给Python。
  2. 将函数的参数写入管道。
  3. 从管道读取答案。

完成。

以下是一些代码片段,展示了如何创建管道:

Process p = Runtime.getRuntime().exec(commande);
BufferedReader output = getOutput(p); // 从Python到Java的输出
BufferedReader error = getError(p); // 从Python到Java的错误输出
PrintWriter input  = getInput(p); // 从Java到Python的输入

private static BufferedReader getOutput(Process p) {
    return new BufferedReader(new InputStreamReader(p.getInputStream()));
}
private static BufferedReader getError(Process p) {
    return new BufferedReader(new InputStreamReader(p.getErrorStream()));
}
private static PrintWriter getInput(Process p){
    return new PrintWriter(new OutputStreamWriter(p.getOutputStream()));
}
英文:

You could communicate between java and Python with pipes.

You run your Python as you do now (without command line args)

Python script

you write an infinite loop in python that will

  1. read data from the
    standard input (it will be your arg for the function)

  2. You call your function

  3. you write the answer to the standard output

Java

  1. Write a method for sending args to python

  2. write to the pipe the arg of your function

  3. read the answer from the pipe

You're done.

Here is some snippet

Here how you create your tube

        Process p = Runtime.getRuntime().exec(commande);
BufferedReader output = getOutput(p); //from python to java
BufferedReader error = getError(p); //from python to java
PrintWriter input  = getInput(p); //from java to python
private static BufferedReader getOutput(Process p) {
return new BufferedReader(new InputStreamReader(p.getInputStream()));
}
private static BufferedReader getError(Process p) {
return new BufferedReader(new InputStreamReader(p.getErrorStream()));
}    
private static PrintWriter getInput(Process p){
return new PrintWriter (new OutputStreamWriter(p.getOutputStream()));
}

答案2

得分: 1

Sure, here's the translated content:

尝试在您的Python脚本中使用以下代码

import sys

def fun(x):
    print(x)
    print('END')
    sys.stdout.flush()

while 1:
    try:
        line = sys.stdin.readline()
    except KeyboardInterrupt:
        break

    if not line:
        break

    fun(line)

您的Java代码应该是基本正确的。

英文:

Try the following in your Python script:

import sys
def fun(x):
print(x)
print(&#39;END&#39;)
sys.stdout.flush()
while 1:
try:
line = sys.stdin.readline()
except KeyboardInterrupt:
break
if not line:
break
fun(line)

Your Java code should be more or less correct.

huangapple
  • 本文由 发表于 2020年4月7日 07:47:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/61070674.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定