用Java執行Python:Jython踩坑筆記

巔峰小學生發表於2018-04-16

常見的java呼叫python指令碼方式

1.通過Jython.jar提供的類庫實現
2.通過Runtime.getRuntime()開啟程式來執行指令碼檔案


1.Jython

Jpython使用時,版本很重要!大多數坑來源於此。這句話不聽的人還得走點彎路

執行環境:Python2.7 + Jython-standalone-2.7.0

<!--Maven依賴,jar包自行前往倉庫下載-->
<dependency>
    <groupId>org.python</groupId>
    <artifactId>jython-standalone</artifactId>
    <version>2.7.0</version>
</dependency>

1)Jython執行Python語句

import org.python.util.PythonInterpreter;

public class HelloPython {
    public static void main(String[] args) {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.exec("print(`hello`)");
    }
}

2)Jython執行Python指令碼

import org.python.util.PythonInterpreter;

public class HelloPython {
    public static void main(String[] args) {
        PythonInterpreter interpreter = new PythonInterpreter();
        interpreter.execfile("./pythonSrc/time.py");
    }
}

3)Jython執行Python方法獲取返回值

PythonInterpreter interpreter = new PythonInterpreter();
interpreter = new PythonInterpreter(); 
interpreter.execfile("./pythonSrc/fibo.py"); 
PyFunction function = (PyFunction)interpreter.get("fib",PyFunction.class); 
PyObject o = function.__call__(new PyInteger(8));
System.out.println(o.toString());

fibo.py

 # Fibonacci numbers module  
def fib(n): # return Fibonacci series up to n  
    result = []  
    a, b = 0, 1  
    while b < n:  
        result.append(b)  
        a, b = b, a+b  ·
    return result 

2.Jython的侷限

Jython在執行普通py指令碼時速度很慢,而且在含有第三方庫(requests, jieba…)時bug很多,不易處理。 原因在於,python執行時的sys.path和Jython的sys.path路徑不一致,以及Jython的處理不是很好。

python執行時:

[`F:\Eclipse for Java EE\workspace\Jython\pythonSrc`, `F:\Python27\DLLs`, `F:\Python27\lib`, `F:\Python27\lib\lib-tk`, `F:\Python27`, `F:\Python27\lib\site-packages`, `F:\Python27\lib\site-packages\unknown-0.0.0-py2.7.egg`, `F:\Python27\lib\site-packages\requests-2.18.4-py2.7.egg`, `F:\Python27\lib\site-packages\certifi-2018.1.18-py2.7.egg`, `F:\Python27\lib\site-packages\urllib3-1.22-py2.7.egg`, `F:\Python27\lib\site-packages\idna-2.6-py2.7.egg`, `F:\Python27\lib\site-packages\chardet-3.0.4-py2.7.egg`, `C:\windows\system32\python27.zip`, `F:\Python27\lib\plat-win`]

Jython 執行時:

[`F:\Maven\repo\org\python\jython-standalone\2.7.0\Lib`, `F:\Maven\repo\org\python\jython-standalone\2.7.0\jython-standalone-2.7.0.jar\Lib`, `__classpath__`, `__pyclasspath__/`]

關於路徑問題,我們有兩種解決方法,一是手動新增第三方庫路徑,呼叫

        PySystemState sys = Py.getSystemState(); 
        System.out.println(sys.path.toString());
        sys.path.add("F:\Python27\Lib\site-packages\jieba"); 

二是把第三方庫資料夾放到執行的.py指令碼同級目錄
然後新的問題來了,你以為路徑是最終的問題嗎?不止,也許是Python的版本語法問題,2x,3x,導致你在用Jython執行含第三方庫的.py指令碼時,各種Module不存在。原本博主走的Jython的路子,還下載了jieba第三方庫,後來執行時一大堆錯誤:jieba庫好像對py3有過渡支援,jython不支援這種語法格式,我改了jieba一處又一處,所以,博主在受過摧殘之後果斷放棄Jython!因為不能使用第三方庫的python,然並卵!

最終方法來了:模擬控制檯執行

public class Cmd {

    public static void main(String[] args) throws IOException, InterruptedException {
        String[] arguments = new String[] { "python", "./pythonSrc/time.py", "huzhiwei", "25" };
        try {
            Process process = Runtime.getRuntime().exec(arguments);
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println(line);
            }
            in.close();
            int re = process.waitFor();
            System.out.println(re);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

time.py

#!/usr/bin/python
#coding=utf-8

#定義一個方法
def my_test(name, age):
    print("name: "+str(name))
    print(age)  #str()防解碼出錯
    return "success"
#主程式
#sys.argv[1]獲取cmd輸入的引數
my_test(sys.argv[1], sys.argv[2])

執行結果

name: huzhiwei
25
0

再嘮兩塊錢的~
這個方法的侷限性也來了,對python開發人員很簡單,直接列印輸出,但一個python模組只能做一件事,這點很像Python端給Java端的一個公開介面,類似Servlet吧?好處也有,不會出錯,執行快!還在猶豫的同學趕快轉cmd吧~!


2018-4-19:

博主在模擬cmd呼叫Python時遇到一些情況,這類問題可以歸類為“超時,阻塞”等
問題原因:

Process p=Runtime.getRuntime().exec(String[] cmd);
Runtime.exec方法將產生一個本地的程式,並返回一個Process子類的例項,該例項可用於控制程式或取得程式的相關資訊。
由於呼叫Runtime.exec方法所建立的子程式沒有自己的終端或控制檯,因此該子程式的標準IO(如stdin,stdou,stderr)都通過
    p.getOutputStream(),
    p.getInputStream(),
    p.getErrorStream()
方法重定向給它的父程式了.使用者需要用這些stream來向 子程式輸入資料或獲取子程式的輸出。
    例如:Runtime.getRuntime().exec("ls")
另外需要關心的是Runtime.getRuntime().exec()中產生停滯(阻塞,blocking)的問題?
這個是因為Runtime.getRuntime().exec()要自己去處理stdout和stderr的輸出,
就是說,執行的結果不知道是現有錯誤輸出(stderr),還是現有標準輸出(stdout)。
你無法判斷到底那個先輸出,所以可能無法讀取輸出,而一直阻塞。
例如:你先處理標準輸出(stdout),但是處理的結果是先有錯誤輸出(stderr),
一直在等錯誤輸出(stderr)被取走了,才到標準輸出(stdout),這樣就產生了阻塞。

解決辦法:

 用兩個執行緒將標準輸出(stdout)和錯誤輸出(stderr)。

完整程式碼:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Created by yster@foxmail.com 2018年4月19日 下午1:50:06
 */
public class ExcuteCmd {
    /** 執行外部程式,並獲取標準輸出 */
    public static String excuteCmd_multiThread(String[] cmd, String encoding) {
        BufferedReader bReader = null;
        InputStreamReader sReader = null;
        try {
            Process p = Runtime.getRuntime().exec(cmd);

            /* 為"錯誤輸出流"單獨開一個執行緒讀取之,否則會造成標準輸出流的阻塞 */
            Thread t = new Thread(new InputStreamRunnable(p.getErrorStream(), "ErrorStream"));
            t.start();

            /* "標準輸出流"就在當前方法中讀取 */
            BufferedInputStream bis = new BufferedInputStream(p.getInputStream());

            if (encoding != null && encoding.length() != 0) {
                sReader = new InputStreamReader(bis, encoding);// 設定編碼方式
            } else {
                sReader = new InputStreamReader(bis, "utf-8");
            }
            bReader = new BufferedReader(sReader);

            StringBuilder sb = new StringBuilder();
            String line;

            while ((line = bReader.readLine()) != null) {
                sb.append(line);
                sb.append("
");
            }

            bReader.close();
            p.destroy();
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "result: error";
        } finally {
        }
    }
}

class InputStreamRunnable implements Runnable {
    BufferedReader bReader = null;
    String type = null;

    public InputStreamRunnable(InputStream is, String _type) {
        try {
            bReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(is), "UTF-8"));
            type = _type;
        } catch (Exception ex) {
        }
    }

    @SuppressWarnings("unused")
    public void run() {
        String line;
        int lineNum = 0;

        try {
            while ((line = bReader.readLine()) != null) {
                lineNum++;
                // Thread.sleep(200);
            }
            bReader.close();
        } catch (Exception ex) {
        }
    }
}

使用時直接呼叫該工具類即可。


相關文章