一樣是公司需求,因為AI Team都是使用Python開發演算法程式,所以要使用演算法就一定要可以執行Python,但是前端的UI使用的程式語言不可能都能完整與Python整合,所以有了兩種替代方案,第一種是架設一個Web service,而且就是使用Python來架設,如此就可以在Web Service這一層直接呼叫其他部門開發的Python程式;第二種是透過command line的方式(後述)。這一篇接著講的是如何運用第二種方式提供UI一個類似運算中心的feature。
承接第一篇文章的目的,某一支程式要跟另一隻程式做溝通的方式還有call out,就是在command line的模式下呼叫,並且雙方定義好input以及output,就可以達到溝通的目的。但是這樣的方式通常會有版本控管的問題,因為這種做法是執行某支特定檔案,當UI是採取Windows form的方式在User端執行,那麼要怎麼換掉那隻檔案就是一個問題。比如說前端UI不變,但是想撤換後端做運算的Python程式時,就必須要有一個更新的機制提供給User自動或手動更新,在架構上沒辦法像第一篇的Web Service切得這麼乾淨,可以各自發展、各自維護。
另外一個問題就是,User端一定要安裝Python的執行環境,這個通常也會是最令人頭痛的,除非跟infra說好做系統時預設要將Python環境做進去,而且如果UI想換新版本的Python時(例如Python 2.7 to 3.X),也必須跟infra協調重做一堆User的系統。針對時效性以及浪費的成本這兩點來評估,還是會比較推薦採用分層的做法。
廢話講了很多,先進入主題吧,筆者在這邊會以C#傳統的Windows form來當作例子。首先要確定執行環境中已經安裝好Python,如果不確定,可以先打開cmd,然後輸入python看一下OS認不認得這個指令:
OK之後打開Visual Studio,建立一個新的Windows Form專案,Form裡面只要放一個TextBox(Multiline = true)以及Button就可以了。然後點兩下Button進去編輯Click事件:以下是Form1.cs的完整內容:
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
/// <summary>
/// 委派方法
/// </summary>
/// <param name="text"></param>
delegate void SetTextCallback(string text);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//利用實體檔案來當作兩種程式的溝通管道
double timeStamp = ((TimeSpan)DateTime.Now.Subtract(DateTime.MinValue)).TotalSeconds;
//實體檔案名稱
string fileName = string.Format("{0}.txt", timeStamp);
//起一個Process執行Python程式
Process pyProc = new Process();
pyProc.EnableRaisingEvents = true;
pyProc.StartInfo.UseShellExecute = false;
pyProc.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
pyProc.StartInfo.FileName = "python";
pyProc.StartInfo.CreateNoWindow = true;
//將必要的參數丟進Python,其中test.py的路徑是放在與此UI執行路徑的同一個資料夾中
pyProc.StartInfo.Arguments = string.Format("test.py --name \"Albert\" --content \"{0}\" --timestamp \"{1}\"",
//AppDomain.CurrentDomain.BaseDirectory,
DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"),
timeStamp);
//定義Process結束事件處理器
pyProc.Exited += (obj, args) =>
{
//ExitCode: 0 表示Process執行無錯誤,順利結束
if (pyProc.ExitCode == 0)
{
//讀出實體檔案內容,並設定給textBox1.Text(這邊請自行處理無檔案時的例外)。
//這裡會用委派是因為Process是開另一個執行序去處理的,
//所以有thread-unsafe的問題,使用委派是一種解決方式(另一種是使用BackgroundWorker)
//請參考:https://docs.microsoft.com/zh-tw/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls
this.SetText(System.IO.File.ReadAllText(fileName));
File.Delete(fileName);
}
else
{
this.SetText(string.Format("TimeStamp: {0} 執行錯誤", timeStamp));
}
};
bool result = pyProc.Start();
}
/// <summary>
/// 定義委派方法,用來改變畫面上的控制項
/// </summary>
/// <param name="text"></param>
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
}
}
關於程式碼裡面的comment,簡單說就是使用前端指定的檔案名稱來當做Python的回傳資料暫存區,等UI讀取完檔案內容之後就把檔案砍掉,就這麼簡單。其中比較特別的是,如果在pyProc.Exited事件處理器中,直接設定this.textBox1.Text = System.IO.File.ReadAllText(fileName),會在執行階段產生Exception,因為這樣對Windows Form的控制項來說是一種thread-unsafe的操作,所以必須透過委派或是背景工作的方式來操作控制項。
接下來是test.py的完整內容:
# -*- coding: utf-8 -*-
#Python內建的參數處理package
import argparse
#產生一個針對參數的parser
def MakeArgParser():
parser = argparse.ArgumentParser()
#定義每一個參數的名稱、資料型態、預設值、說明等
parser.add_argument('--name', type=str, default=None, help='Name to call')
parser.add_argument('--content', type=str, default=None, help='Content to say')
parser.add_argument('--timestamp', type=str, default=None, help='The File Name of Result')
return parser
if __name__ == '__main__':
parser = MakeArgParser()
#使用parser將command line模式收到的參數轉成args物件
args = parser.parse_args()
#這邊可以再轉呼叫AI Team他們所撰寫的演算法程式,取得結果後轉成json格式的字串寫入txt中
#將需要回傳的資料寫在指定名稱的txt檔案中
with open(args.timestamp + '.txt', mode='w') as resultFile:
resultFile.write("Hello {}, it is {} now!".format(args.name, args.content))
這一支Python程式相對來說更簡單,只有將參數吃進來,再把一段變動的字串寫進指定名稱的txt檔案中。重點在取得args之後,就可以呼叫其他部門提供的演算法程式,取得分析或預測之後的結果,將結果轉成json格式存進檔案中(這一段可以利用pandas.DataFrame.to_json來做,很簡單),前端取得檔案內容之後也可以很便利的將json反序列化成為物件集合來使用。
相對於Web service,這種直接call out的方式對於Python程式碼來說是沒有隱私可言的,所以建議想要保護自己智慧結晶的同學們還是採用Web Service來做整合應用。
Python Version:
3.6.6