Plusieurs façons pour Java d'appeler des fichiers Python

L'opportunité pour java d'appeler python vient d'un projet qui doit utiliser un algorithme, mais le python écrit par les ingénieurs de l'algorithme, il y a donc un besoin pour le backend java d'appeler des scripts python, et de nombreux problèmes ont été rencontrés au milieu , donc je vais l'enregistrer et le trier une fois.

1. Quelles sont les façons d'appeler python depuis java

1.1 Méthode 1 : jpython

La bibliothèque de classes développée spécifiquement pour appeler python2 à partir de Java, mais comme la version python3 n'est pas prise en charge et que la syntaxe entre python2 et 3 n'est pas compatible, la bibliothèque jpython n'est pas particulièrement universelle. Quelqu'un sur github a demandé quand la version python3 de la bibliothèque sera publiée. La réponse officielle est que c'est faisable mais très difficile (en août 2022, l'officiel de jpython n'a pas encore développé de bibliothèque de classes prenant en charge python3)

La syntaxe de jpython est très simple et les fichiers python peuvent être facilement manipulés à l'aide de PythonIntercepter.

1.1.1 Importer le package jar

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

1.1.2 Appeler la méthode method1() dans le script python

 PythonInterpreter interpreter = new PythonInterpreter();
interpreter.execfile("C:\\Users\\Dick\\Desktop\\demo.py");
// 调用demo.py中的method1方法
PyFunction func = interpreter.get("method1",PyFunction.class);
Integer a = 10;
Integer b = 10;
PyObject pyobj = func.__call__(new PyInteger(a), new PyInteger(b));
System.out.println("获得方法的返回值 = " + pyobj.toString());

Remarque : S'il n'y a pas de valeur de retour, exécutez simplement la méthode interpreter.execfile()

1.2 Méthode 2 : ProcessBuilder

ProcessBuilder est une classe d'outil d'exécution de script fournie par jdk. Qu'il s'agisse d'un fichier python, d'un script shell ou d'autres instructions, il peut être exécuté via cette classe. Voyons comment il appelle un script python.

1.2.1 Nous mettons d'abord le fichier python sous ressource

1.2.2 L'étape suivante consiste à exécuter le script

 /**
 * 执行python脚本
 * @param fileName 脚本文件名称
 * @param params 脚本参数
 * @throws IOException
 */
public static void execPythonFile(String fileName, String params) throws IOException {
 
  // 获取python文件所在目录地址
  String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/"; 
 
  // windows执行脚本需要使用 cmd.exe /c 才能正确执行脚本
  Process process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
 
  logger.info("读取python文件 开始 fileName={}", fileName);
  BufferedReader errorReader = null;
  // 脚本执行异常时的输出信息
  errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  List<String> errorString = read(fileName, errorReader);
  logger.info("读取python文件 异常 fileName={}&errorString={}", fileName, errorString);
 
  // 脚本执行正常时的输出信息
  BufferedReader inputReader = null;
  inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  List<String> returnString = read(fileName, inputReader);
  logger.info("读取python文件 fileName={}&returnString={}", fileName, returnString);
 
  try {
      logger.info("读取python文件 wait fileName={}", fileName);
      process.waitFor();
  } catch (InterruptedException e) {
      logger.error("读取python文件 fileName="+fileName+" 等待结果返回异常", e);
  }
  logger.info("读取python文件 fileName={} == 结束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
        List<String> resultList =  Lists.newArrayList();
        String res = "";
        while (true) {
            try {
                if (!((res = reader.readLine()) != null)) break;
            } catch (IOException e) {
                logger.error("读取python文件 fileName=" + fileName + " 读取结果异常", e);
            }
            resultList.add(res);
        }
        return resultList;
}

Le code ci-dessus ne considère que Windows, mais la situation sera plus compliquée sous Linux.

1.2.3 Problèmes existants lors de l'exécution de Python sous Linux

Nous savons que le déploiement de projet conventionnel consiste à empaqueter le projet dans un package jar, puis à le placer directement dans Linux ou à le déployer via un conteneur tel que docker.À ce stade, le fichier py sous ressources se trouve dans le package jar, mais lorsque on exécute le script python, on utilise :

L'emplacement du fichier de script python3,

À l'heure actuelle, le script python se trouve dans le package jar et n'est pas accessible via le chemin jar /BOOT-INF/classes/py/xxx.py [je l'ai testé pendant un certain temps et j'ai trouvé que python3 (les instructions python ne fonctionnent pas) les instructions ne peuvent pas appeler de scripts dans le jar] , donc la solution à laquelle je peux penser est de placer le fichier de script python directement dans un dossier sur le serveur pour un accès ultérieur. S'il s'agit d'un déploiement docker, il vous suffit d'ajouter une commande COPY au dockerfile pour placer le fichier py dans le répertoire spécifié :

1.2.4 Exécuter des fichiers python sous Linux

Le code suivant sera compatible avec Windows et Linux pour appeler le fichier py [Linux exécute le fichier py en utilisant python ou python3 selon la configuration réelle de la variable d'environnement py à choisir]

 /**
* 执行python文件
* @param fileName python文件地址
* @param params 参数 其实可以改成传入多个参数 一个个放入ProcessBuilder中的
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) throws IOException {
  // ① 当前系统类型
  String os = System.getProperty("os.name");
 
  // ② 获取python文件所在目录地址
  String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/";  
  String linuxPath = "/ai/egcc/";
 
  logger.info("读取python文件 init fileName={}&path={}", fileName);
  Process process;
  if (os.startsWith("Windows")){
      // windows执行脚本需要使用 cmd.exe /c 才能正确执行脚本
      process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
  }else {
      // linux执行脚本一般是使用python3 + 文件所在路径
      process = new ProcessBuilder("python3", linuxPath + fileName, params).start();
  }
 
  logger.info("读取python文件 开始 fileName={}", fileName);
  BufferedReader errorReader = null;
  // 脚本执行异常时的输出信息
  errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  List<String> errorString = read(fileName, errorReader);
  logger.info("读取python文件 异常 fileName={}&errorString={}", fileName, errorString);
 
  // 脚本执行正常时的输出信息
  BufferedReader inputReader = null;
  inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  List<String> returnString = read(fileName, inputReader);
  logger.info("读取python文件 fileName={}&returnString={}", fileName, returnString);
 
  try {
      logger.info("读取python文件 wait fileName={}", fileName);
      process.waitFor();
  } catch (InterruptedException e) {
      logger.error("读取python文件 fileName="+fileName+" 等待结果返回异常", e);
  }
  logger.info("读取python文件 fileName={} == 结束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
    List<String> resultList =  Lists.newArrayList();
    String res = "";
    while (true) {
        try {
            if (!((res = reader.readLine()) != null)) break;
        } catch (IOException e) {
            logger.error("读取python文件 fileName=" + fileName + " 读取结果异常", e);
        }
        resultList.add(res);
    }
    return resultList;
}

Pensez-vous que c'est fini ? En fait, il y a quelques problèmes avec la méthode process.waitFor(). Si elle est en ligne, cela peut provoquer des accidents. Pour plus de détails, veuillez vous référer à : java appelle le programme exe pour utiliser process.waitFor( ) impasse

Essayons ensuite d'utiliser le pool de threads pour résoudre le problème de blocage.

1.2.5 Résoudre le problème implicite du fichier de script d'appel java

Voici le code de version ultime :

 private static ExecutorService taskPool = new ThreadPoolExecutor(8, 32
        ,200L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(600)
        ,new ThreadFactoryBuilder()
        .setNameFormat("thread-自定义线程名-runner-%d").build());
/**
* 执行python文件
* @param fileName python文件地址
* @param params 参数 多个直接逗号隔开
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) throws IOException {
    // ① 当前系统类型
    String os = System.getProperty("os.name");
  
    // ② 获取python文件所在目录地址
    String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/";  
    String linuxPath = "/ai/egcc/";
  
    logger.info("读取python文件 init fileName={}&path={}", fileName);
    Process process;
    if (os.startsWith("Windows")){
        // windows执行脚本需要使用 cmd.exe /c 才能正确执行脚本
        process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
    }else {
        // linux执行脚本一般是使用python3 + 文件所在路径
        process = new ProcessBuilder("python3", linuxPath + fileName, params).start();
    }
 
    taskPool.submit(() -> {
        logger.info("读取python文件 开始 fileName={}", fileName);
        BufferedReader errorReader = null;
        // 脚本执行异常时的输出信息
        errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        List<String> errorString = read(fileName, errorReader);
        logger.info("读取python文件 异常 fileName={}&errorString={}", fileName, errorString);
    });
 
    taskPool.submit(() -> {
        // 脚本执行正常时的输出信息
        BufferedReader inputReader = null;
        inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        List<String> returnString = read(fileName, inputReader);
        logger.info("读取python文件 fileName={}&returnString={}", fileName, returnString);
    });
    
    try {
        logger.info("读取python文件 wait fileName={}", fileName);
        process.waitFor();
    } catch (InterruptedException e) {
        logger.error("读取python文件 fileName="+fileName+" 等待结果返回异常", e);
    }
    logger.info("读取python文件 fileName={} == 结束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
    List<String> resultList =  Lists.newArrayList();
    String res = "";
    while (true) {
        try {
            if (!((res = reader.readLine()) != null)) break;
        } catch (IOException e) {
            logger.error("读取python文件 fileName=" + fileName + " 读取结果异常", e);
        }
        resultList.add(res);
    }
    return resultList;
}

Eh bien, le code ci-dessus peut déjà appeler correctement le script python, mais le blogueur a encore quelques problèmes à résoudre : par exemple, comment appeler le fichier py à l'intérieur du package jar java ? Le fichier py dans le package jar sous Windows peut être appelé avec succès [j'ai testé le package jar localement sous Windows], mais le fichier jar dans le conteneur Docker ne peut pas être appelé avec succès.

Si quelqu'un rencontre des problèmes, veuillez laisser un message et discuter dans la zone de commentaires

1.2.6 La classe d'outils d'exécution python ultime [utilisation recommandée]

 import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * java调用python的执行器
 */
@Component
public class PythonExecutor {
    private static final Logger logger = LoggerFactory.getLogger(PythonExecutor.class);
    private static final String OS = System.getProperty("os.name");
 
    private static final String WINDOWS_PATH = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/automl/";  // windows为获取项目根路径即可
    private static final String LINUX_PATH = "/ai/xx";// linux为python文件所在目录
 
    private static ExecutorService taskPool = new ThreadPoolExecutor(8, 16
            , 200L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(600)
            , new ThreadFactoryBuilder()
            .setNameFormat("thread-自定义线程名-runner-%d").build());
 
    /**
     * 执行python文件【异步 无需等待py文件执行完毕】
     *
     * @param fileName python文件地址
     * @param params   参数
     * @throws IOException
     */
    public static void execPythonFile(String fileName, String params) {
        taskPool.submit(() -> {
            try {
                exec(fileName, params);
            } catch (IOException e) {
                logger.error("读取python文件 fileName=" + fileName + " 异常", e);
            }
        });
 
    }
 
    /**
     * 执行python文件 【同步 会等待py执行完毕】
     *
     * @param fileName python文件地址
     * @param params   参数
     * @throws IOException
     */
    public static void execPythonFileSync(String fileName, String params) {
        try {
            execSync(fileName, params);
        } catch (IOException e) {
            logger.error("读取python文件 fileName=" + fileName + " 异常", e);
        }
    }
 
    private static void exec(String fileName, String params) throws IOException {
        logger.info("读取python文件 init fileName={}&path={}", fileName, WINDOWS_PATH);
        Process process;
        if (OS.startsWith("Windows")) {
            // windows执行脚本需要使用 cmd.exe /c 才能正确执行脚本
            process = new ProcessBuilder("cmd.exe", "/c", "python", WINDOWS_PATH + fileName, params).start();
        } else {
            // linux执行脚本一般是使用python3 + 文件所在路径
            process = new ProcessBuilder("python3", LINUX_PATH + fileName, params).start();
        }
 
        new Thread(() -> {
            logger.info("读取python文件 开始 fileName={}", fileName);
            BufferedReader errorReader = null;
            // 脚本执行异常时的输出信息
            errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            List<String> errorString = read(fileName, errorReader);
            logger.info("读取python文件 异常 fileName={}&errorString={}", fileName, errorString);
        }).start();
 
        new Thread(() -> {
            // 脚本执行正常时的输出信息
            BufferedReader inputReader = null;
            inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            List<String> returnString = read(fileName, inputReader);
            logger.info("读取python文件 fileName={}&returnString={}", fileName, returnString);
        }).start();
 
        try {
            logger.info("读取python文件 wait fileName={}", fileName);
            process.waitFor();
        } catch (InterruptedException e) {
            logger.error("读取python文件 fileName=" + fileName + " 等待结果返回异常", e);
        }
        logger.info("读取python文件 fileName={} == 结束 ==", fileName);
    }
 
    private static void execSync(String fileName, String params) throws IOException {
        logger.info("同步读取python文件 init fileName={}&path={}", fileName, WINDOWS_PATH);
        Process process;
        if (OS.startsWith("Windows")) {
            // windows执行脚本需要使用 cmd.exe /c 才能正确执行脚本
            process = new ProcessBuilder("cmd.exe", "/c", "python", WINDOWS_PATH + fileName, params).start();
        } else {
            // linux执行脚本一般是使用python3 + 文件所在路径
            process = new ProcessBuilder("python3", LINUX_PATH + fileName, params).start();
        }
 
        taskPool.submit(() -> {
            logger.info("读取python文件 开始 fileName={}", fileName);
            BufferedReader errorReader = null;
            // 脚本执行异常时的输出信息
            errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            List<String> errorString = read(fileName, errorReader);
            logger.info("读取python文件 异常 fileName={}&errorString={}", fileName, errorString);
        });
 
        taskPool.submit(() -> {
            // 脚本执行正常时的输出信息
            BufferedReader inputReader = null;
            inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            List<String> returnString = read(fileName, inputReader);
            logger.info("读取python文件 fileName={}&returnString={}", fileName, returnString);
        });
 
        try {
            logger.info("同步读取python文件 wait fileName={}", fileName);
            process.waitFor();
        } catch (InterruptedException e) {
            logger.error("同步读取python文件 fileName=" + fileName + " 等待结果返回异常", e);
        }
        logger.info("同步读取python文件 fileName={} == 结束 ==", fileName);
    }
 
    private static List<String> read(String fileName, BufferedReader reader) {
        List<String> resultList = Lists.newArrayList();
        String res = "";
        while (true) {
            try {
                if (!((res = reader.readLine()) != null)) break;
            } catch (IOException e) {
                logger.error("读取python文件 fileName=" + fileName + " 读取结果异常", e);
            }
            resultList.add(res);
        }
        return resultList;
    }
 
}
Runtime.getRuntime().exec()

En fait, la couche inférieure du script ci-dessus utilise également l'objet ProcessBuilder, c'est donc la même chose.

Je suppose que tu aimes

Origine blog.csdn.net/zhangjiaming_zjm/article/details/128700807
conseillé
Classement