Java 调用Python脚本并传递参数

     在一些复杂的应用场景中,脚本语言可能只是充当实现基础功能的模块,接受其它编程语言模块的“调遣”,在此,我以Java语言为例,介绍调用Python模块的方法。

1 基于Java.lang.Process调用Python脚本

1.1 Process概述

    Process类是一个抽象类(所有的方法均是抽象的),封装了一个进程(即一个执行程序)。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法。 ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,该实例可用来控制进程并获取相关信息。 
     创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。 
    当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程

1.2 如何创建Process对象,一般有两种方法: 

  • 每个 ProcessBuilder 实例管理一个进程属性集。start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。(ProcessBuilder这个是JDK5中新添加的final类,详细请参看“深入研究java.lang.ProcessBuilder类”一文。 
  • Runtime.exec() 方法创建一个本机进程,并返回 Process 子类的一个实例。

2 调用实例一(无传参调用)

编写一个Java程序,调用Python脚本TestMain.py,代码如下:

public static void main(String[] args) throws IOException, InterruptedException
    {
        // define the command string        
        String commandStr = new String(
                "python D:\\Users\\workspace\\learn-python\\com\\demo\\TestMain.py");
        //Create a Process instance and execute commands
        Process pr = Runtime.getRuntime().exec(commandStr);
        //Get the result produced by executing the above commands
        BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        String line = null;
        String result = "";
        while ((line = in.readLine()) != null)
        {
            result += line + "\r\n";
        }
        System.out.println(result);

        in.close();
        int endFlag = pr.waitFor();
        if (endFlag == 0)
        {
            System.out.println("The process is ended normally.");
        }
    }

TestMain.py中代码如下:

if __name__ == '__main__':    
    print('------------------------begin---------------------------')   
    print('just for testing!') 
    print('-------------------------end----------------------------')

运行结果:

【解释】:为什么运行的结果和直接运行Python脚本一样呢?
    由于我们在Python脚本中,打印信息采用的是print函数,这是一个将信息打印到控制台或者终端的函数,在Process类的介绍中已经提及:Process进程实例(子进程)相对于调用它的Java进程(父进程)是父子关系,Process实例并没有自己的控制台或终端用于打印脚本执行的输出信息,可通过方法getInputStream()将输出重定向到调用脚本的父进程,进而输出。
    通过这种机制,Java程序可以方便的调用Python脚本,并获得脚本执行的结果(脚本中须采用print打印调用方需要的结果)。

3 调用实例二(传参调用)

    实例一中,Java只是单纯调用Python,并没有传递参数,执行的是“写死”的脚本,这属于低级别的简单调用。更多的场景下,Java调用Python脚本时往往需要传递参数,作为Python脚本中被调函数的入参,以便执行更加灵活的任务。为阐明该用法,我们需要编写一个可接收参数的Python脚本,代码如下:sys.argv用于接收调用方传递的参数
import sys

if __name__ == '__main__': 
    print('------------------------begin---------------------------')
    #Get the parameter list 
    parameterList=sys.argv
           
    print(parameterList)  
      
    print('-------------------------end----------------------------')

Java调用代码如下:

public static void main(String[] args) throws IOException, InterruptedException
    {
        // define the command string        
        String commandStr = new String(
                "python D:\\Users\\workspace\\learn-python\\com\\demo\\TestMain.py param1 param2 param3");
        //Create a Process instance and execute commands
        Process pr = Runtime.getRuntime().exec(commandStr);
        //Get the result produced by executing the above commands
        BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        String line = null;
        String result = "";
        while ((line = in.readLine()) != null)
        {
            result += line + "\r\n";
        }
        System.out.println(result);

        in.close();
        int endFlag = pr.waitFor();
        if (endFlag == 0)
        {
            System.out.println("The process is ended normally.");
        }
    }

执行Java代码输出Python脚本重定向打印的结果:

------------------------begin---------------------------

['D:\\Users\\workspace\\learn-python\\com\\demo\\TestMain.py', 'param1', 'param2', 'param3']

------------------------end---------------------------

The process is ended normally.

    很明显,Java调用时传递的参数被Python脚本正确接收,并根据空格分离开来,其中第一个为脚本名称,在实际应用中,我们通过sys.argv[1:]过滤掉第一个参数。

    初步尝试了调用的快感,如果就此打住,显然是不明智的,继续探索更加复杂的调用。比如,我们需要同时传递,数字、字符串、列表参数:120 “commoncommands” listTemp[param1, param1, param1],并且要求参数的数量可变,怎么办呢?    

    其实很简单,传递的参数是以字符串的形式传递的,所有的参数都混在一个字符串中,我们只要接收字符串,并解析即可。在Python中,就有这么一个用于解析命令参数的函数:

def getopt(args, shortopts, longopts=[]):

【参数解释】
  1. args:args为需要解析的参数列表。一般使用sys.argv[1:],这样可以过滤掉第一个参数(ps:第一个参数是脚本的名称,它不应该作为参数进行解析)
  2. shortopts:简写参数列表
  3. longopts:长参数列表
【返回值】
  1. opts:分析出的(option, value)列表对。
  2. args:不属于格式信息的剩余命令行参数列表。
实例代码如下:
Java代码:
public static void main(String[] args) throws IOException, InterruptedException
    {
        // define the command string        
        String commandStr = new String(
                "python D:\\Users\\workspace\\learn-python\\com\\demo\\TestMain.py -b Apple -l type1=common-log,type2=advance-log -t startTime=1502770332956,endTime=1502856732956");
        //Create a Process instance and execute commands
        Process pr = Runtime.getRuntime().exec(commandStr);
        //Get the result produced by executing the above commands
        BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
        String line = null;
        String result = "";
        while ((line = in.readLine()) != null)
        {
            result += line + "\r\n";
        }
        System.out.println(result);
        in.close();
        pr.waitFor();
    }

Python代码

import sys
import getopt

if __name__ == '__main__': 
    print('------------------------begin---------------------------')
    #Get the parameter list 
    parameterList=sys.argv[1:]           
    #initialize all parameters
    businessType=''
    logType = {}
    timeInfo = {}
    opts, args = getopt.getopt(parameterList,"b:l:t:",['businessType=','logType=','timeInfo'])
    for opt, arg in opts:
        if opt in ("-b", "--businessType"):
            businessType = arg
        elif opt in ("-l", "--logType"):
            logType = dict(item.split('=') for item in arg.split(','))
        elif opt in ("-t", "--timeInfo"):
            timeInfo = dict(item.split('=') for item in arg.split(','))
    
    print('businessType:',businessType)
    print('logType:',logType)
    print('timeInfo:',timeInfo)
    
    print('-------------------------end----------------------------')

运行结果:



你可能感兴趣的:(Java,Python)