1. 程式人生 > 實用技巧 >python 打包exe程式

python 打包exe程式

安裝python環境

https://www.python.org/downloads/windows/

安裝pyinstaller

pip install pyinstaller

準備python原始碼


from tkinter import Tk
from tkinter import Button
from tkinter import INSERT
from tkinter import END
from tkinter import WORD
from tkinter import BOTH
from tkinter.scrolledtext import ScrolledText
import tkinter.filedialog
import tkinter.messagebox
import xml.dom.minidom
import os
import re
import string

g_timeout = 4   # 4秒超時


# 通訊引數
g_param = {
    "ProtocolType": "0x00",
    "PHYPort": "0x00",
    "FunctionAddress": "0x00",
    "ECUAddress": "0x00",
    "TesterAddress": "0xF1",
    "Baudrate": "500",
    "TIdle": "300",
    "TWup": "50",
    "P1Min": "0",
    "P1Max": "20",
    "P2Min": "25",
    "P2Max": "50",
    "P3Min": "55",
    "P3Max": "5000",
    "P4Min": "5",
    "P4Max": "20",
    "CanPhysReqId": "0x710",
    "CanFuncReqId": "0x7DF",
    "Equipment_Serv_Id": "0x00",
    "Ecu_Time_T1": "0x00",
    "Ecu_Time_T2": "0x00",
    "Ecu_Time_T3": "0x00",
    "Ecu_Time_T4": "0x00",
    "Tester_Time_T1": "0x00",
    "Tester_Time_T2": "0x00",
    "Tester_Time_T3": "0x00",
    "Tester_Time_T4": "0x00",
}

# 日誌中解析的報文結構
class Packet:
    id = ""
    thread = ""
    process = ""
    time = 0
    content = []
    content_string = ""
    valid = True

    def __init__(self, id, thread, process ,content, content_string, time):
        self.id = id
        self.thread = thread
        self.process = process
        self.content = content
        self.content_string = content_string
        self.time = time

# 對應xml
class Service:
    req = ""
    res = ""

    def __init__(self, req, res):
        self.req = req
        self.res = res

# 對應xml
class Section:
    name = ""
    sid = ""
    rid = ""
    services = []

    def __init__(self, sid, rid):
        self.name = sid + "_" + rid
        self.sid = sid
        self.rid = rid
        self.services = []

class Main():
    packets = []
    xmlFile = []

    def __init__(self):
        self.win = Tk()
        self.initWin()
        self.log = ScrolledText(self.win, wrap=WORD, height=40)
        self.log.pack(side="bottom", fill=BOTH)
        self.initBtn()
        self.win.mainloop()

    def initWin(self):
        self.win.title("Log")
        self.win.geometry("600x600")
        self.win.resizable(width=False,height=False)

    def initBtn(self):
        Button(self.win, text="選擇日誌檔案..", command=self.GetResource).place(x=10, y=10)
        Button(self.win, text="匯出結果檔案", command=self.GenerateXML).place(x=110, y=10)

    # 開啟檔案 獲取所有報文內容
    def GetResource(self):
        default_dir = r"C:\\"
        self.filePath = tkinter.filedialog.askopenfilename(title=u'選擇檔案', initialdir=(os.path.expanduser(default_dir)))

        try:
            f= open(self.filePath,  'r', encoding='UTF-8', errors='ignore')
        except IOError as e:
            Tk.messagebox.showerror(title = '錯誤', message='開啟檔案失敗')
            return
        
        patternPacket = re.compile('^[A-Z]+ ([0-9]*) (?P<time>[0-9]*)\.([0-9]*) (?P<thread_num>[a-zA-Z0-9]*)::(?P<process_num>[a-zA-Z0-9]*) \[([a-zA-Z]*)\] OS[<|>] (?P<id>[a-zA-Z0-9]*) (?P<content>[^"]*)')
        patternOdbParam = re.compile('^[A-Z]+ ([0-9]*) ([0-9]*)\.([0-9]*) ([a-zA-Z0-9]*)::([a-zA-Z0-9]*) \[([a-zA-Z]*)\] ObdOpen\(port=([0-9]*),proto=([0-9]*),rate=([a-zA-Z0-9]*),fadr=([a-zA-Z0-9]*),eadr=([a-zA-Z0-9]*),tadr=([a-zA-Z0-9]*),sid=0x(?P<sid>[a-zA-Z0-9]*),rid=0x(?P<rid>[a-zA-Z0-9]*)\)')
        
        self.log.delete(0.0, END)
        lines = f.readlines()
        for line in lines:
            if (-1 != line.find("[vcicore] ObdOpen(port=")):
                regMatch = patternOdbParam.match(line)
                if None != regMatch:
                    linebits = regMatch.groupdict()
                    sid = linebits['sid']
                    rid = linebits['rid']
                    # 去重
                    if (self.RemoveDuplication(sid, rid)):
                        section = Section(sid, rid)
                        self.xmlFile.append(section)
            if (-1 != line.find("[vcicore] OS")):
                regMatch = patternPacket.match(line)
                if None != regMatch:
                    linebits = regMatch.groupdict()
                    content_string = content = linebits['content'].replace("\n","")
                    content = content_string.split(' ')
                    # 36刷寫報文只取部分
                    if (len(content) > 20
                    and content[0] == "36"):
                        content = content[:3]
                        content_string = content_string[:8]
                    packet = Packet(linebits['id'], linebits['thread_num'], linebits['process_num'], content, content_string, int(linebits['time']))    
                    self.log.insert(INSERT, linebits['id']+ " " +content_string +'\n')
                    self.packets.append(packet)
        f.close()

    # 生成xml檔案
    def GenerateXML(self):
        cnt = 0
        for element in self.xmlFile:
            self.Organize(element)
            if (len(element.services) != 0):
                self.CreateFile(element)
                cnt = cnt + 1
        tkinter.messagebox.showwarning(title = '完成',message='共生成'+str(cnt)+'個檔案')
        self.filePath = ""

    # 判斷是否重複
    def RemoveDuplication(self, sid, rid):
        for e in self.xmlFile:
            if (sid == e.sid and rid == e.rid):
                return False
        return True

    # 驗證是否是對應的收發報文
    def Calibrate(self, req, res):
        if (req.process == res.process and req.thread == res.thread):
            if (len(req.content) > 0 and len(res.content) > 0):
                    if (res.content[0] == "7F"): #7F的特殊情況
                        return True
                    if (64 == int(res.content[0], 16) - int(req.content[0], 16)):
                        return True
        return False

    # 整理報文對應的檔案
    def Organize(self, section):
        cnt_record = 0

        while (True):
            if (0 == len(self.packets)):
                break   # 報文列表為空,退出

            #找到一個傳送id
            cnt_sid = -1
            index = cnt_record
            while (index < len(self.packets)):
                if (self.packets[index].valid
                    and section.sid == self.packets[index].id):
                    cnt_sid = index
                    #找到的傳送報文設定無效標記
                    self.packets[index].valid = False
                    break
                index += 1
                cnt_record = index
            
            #sid未找到,列表中沒有需要的報文,退出
            if (-1 == cnt_sid):
                break

            #找到報文傳送id對應的接收id
            cnt_rid = -1
            index = cnt_sid + 1
            sid_head = self.packets[cnt_sid].content[0] #傳送報文的頭部
            max_time = self.packets[cnt_sid].time + g_timeout #最大尋找時間
            while (index < len(self.packets)):
                if (self.packets[index].valid
                    and section.rid == self.packets[index].id
                    and self.packets[index].time < max_time):
                    #判斷是延時幀(7F xx 78)
                    if (len(self.packets) > 2
                        and self.packets[index].content[0] == "7F"
                        and self.packets[index].content[1] == sid_head
                        and self.packets[index].content[2] == "78"):
                        max_time = self.packets[index].time + g_timeout # 更新超時時間,以當前78報文為準
                        self.packets[index].valid = False # 78報文設定無效標記
                        index += 1
                        continue
                    if (self.Calibrate(self.packets[cnt_sid], self.packets[index])): # 通過執行緒號等驗證是否是對應的接收報文
                        cnt_rid = index # 找到了接收的報文
                        break
                index += 1

            #rid未找到,超時
            if (-1 == cnt_rid):
                continue

            #新增列表
            svc = Service(self.packets[cnt_sid].content_string, self.packets[cnt_rid].content_string)
            self.log.see(END)
            section.services.append(svc)
            self.packets[cnt_rid].valid = False

    # 建立xml檔案
    def CreateFile(self, resultFile):
        impl = xml.dom.minidom.getDOMImplementation()
        # 設定根結點
        dom = impl.createDocument(None, 'ECU', None)
        root = dom.documentElement
        root.setAttribute("name", resultFile.name)

        # 根節點新增子節點COMMPARAMETERS
        commparameters = dom.createElement('COMMPARAMETERS')
        root.appendChild(commparameters)

        # 設定通訊引數
        for key, value in g_param.items():
            nameE=dom.createElement('PARAM')
            nameE.setAttribute("name", key)
            nameE.setAttribute("value", value)
            commparameters.appendChild(nameE)
    
        # 設定canid
        nameE=dom.createElement('PARAM')
        nameE.setAttribute("name", "CANSID")
        nameE.setAttribute("value",resultFile.sid)
        commparameters.appendChild(nameE)

        nameE=dom.createElement('PARAM')
        nameE.setAttribute("name", "CANRID")
        nameE.setAttribute("value", resultFile.sid)
        commparameters.appendChild(nameE)

        # 根節點新增子節點SECTIONS
        sections = dom.createElement('SECTIONS')
        root.appendChild(sections)

        # SECTION
        section=dom.createElement('SECTION')
        sections.appendChild(section)

        for svc in resultFile.services:
            #SERVICE
            service=dom.createElement('SERVICE')
            section.appendChild(service)
            #REQ
            req=dom.createElement('REQ')
            reqTxt=dom.createTextNode(svc.req)  #內容
            req.appendChild(reqTxt)
            service.appendChild(req)
            #RES
            res=dom.createElement('RES')
            resTxt=dom.createTextNode(svc.res)  #內容
            res.appendChild(resTxt)
            service.appendChild(res)
    
        f= open(resultFile.name + '.xml', 'w') #w替換為a,追加
        dom.writexml(f, addindent='\t', newl='\n')
        f.close()

if __name__ == '__main__':
	Main()

打包程式

# -w 隱藏控制檯
pyinstaller -F -w program.py