您好,登錄后才能下訂單哦!
小編這次要給大家分享的是pyinstaller打包成無控制臺程序時運行出錯怎么辦,文章內容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。
有時候我們需要在程序里執行一些cmd命令,使用os或者其它模塊中的popen方法去執行
這個問題一般是程序內有輸入導致的,這個輸入可以是input()
,也可以是其它的一些stdin操作(如os.popen實際上會造成輸入請求)
本質上就是:使用-w
參數(無控制臺)打包時程序里不要請求輸入
或者,你也可以不用-w
參數,手動隱藏控制臺!
有一天,我把使用了os.popen方法的python程序用pyinstaller打包成exe(用了無控制臺打包參數-w
)
雙擊運行時程序卻彈框報錯!
我就有點納悶:為什么有控制臺打包出來的exe(不使用-w
參數)可以運行,使用-w
參數(無控制臺)打包的卻不能運行呢?
首先,調用os.popen
部分的代碼大概是下面這樣的:
with os.popen('taskkill /f /t /im nginx.exe') as re: # 殺掉nginx result = re.read()
執行cmd,殺死nginx。
經過研究,上結論:
os.popen
會打開一個管道執行命令,而管道是有輸入(stdin)、輸出(stdout) 的!
重點就在輸入(stdin)這里:
當我們使用pyinstaller的-w
參數(或Console=False
)打包exe時,python解釋器是不帶控制臺的,
所以它沒有辦法處理輸入(stdin) !
包括使用python的input()
函數也是不行的,都會彈框報錯。
那么怎么辦呢?接著看!
os.popen
實際上是一個簡單的封裝,我們先來看他的原型:subprocess.popen
subprocess.Popen( args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0 )
簡單的解釋一下(詳細請看官方文檔):
subprocess官方文檔:https://docs.python.org/2/library/subprocess.html
懶得看解釋可以直接跳過下面這段,直接看解決方法
args
是一個字符串(如cmd命令),或者是包含程序參數的列表。要執行的程序一般就是這個列表的第一項,或者是字符串本身。但是也可以用executable
參數來明確指出。當executable
參數不為空時,args
里的第一項被認為是“命令名”,不同于真正的可執行文件的文件名,這個“命令名”是一個用來顯示的名稱,例如執行unix/linux下的 ps
命令,顯示出來的就是這個“命令名”。
bufsize
作用就跟python函數open()
的buffering
參數一樣:0表示不緩沖,1表示行緩沖,其他正數表示近似的緩沖區字節數,負數表示使用系統默認值。默認是0。
executable
參數指定要執行的程序。它很少會被用到,一般程序可以由args參數指定。如果shell
參數為True
,executable可以用于指定用哪個shell來執行(比如bash、csh、zsh等)。windows下,只有當你要執行的命令是shell內建命令(比如dir
,copy
等) 時,你才需要指定shell=True
,而當你要執行一個基于命令行的批處理腳本(bat啥的)的時候,不需要指定此項。
stdin
、stdout
和stderr
分別表示子程序的標準輸入、標準輸出和標準錯誤。 可選的值有PIPE
或者一個有效的文件描述符(其實是個正整數)或者一個文件對象,還有None。如果是PIPE
,則表示需要創建一個新的管道,如果是 None
,不會做任何重定向工作,子進程的文件描述符會繼承父進程的。另外,stderr
的值還可以是STDOUT
,表示子進程的標準錯誤也輸出到標準輸出。
如果把preexec_fn
設置為一個可調用的對象(比如函數),就會在子進程被執行前被調用。(僅限unix/linux)
如果把close_fds
設置成True,unix/linux下會在開子進程前把除了0、1、2以外的文件描述符都先關閉。在 Windows下也不會繼承其他文件描述符。
如果把shell
設置成True
,指定的命令會在shell
里解釋執行,這個前面已經說得比較詳細了。
如果cwd
(工作目錄)不是None
,則會把cwd做為子程序的當前目錄。注意,并不會把該目錄做為可執行文件的搜索目錄,所以不要把程序文件所在目錄設置為cwd。
如果env
不是None
,則子程序的環境變量由env的值來設置,而不是默認那樣繼承父進程的環境變量。注意,即使你只在env里定義了某一個環境變量的值,也會阻止子程序得到其他的父進程的環境變量(也就是說,如果env里只有1項,那么子進程的環境變量就 只有1個了)。
如果把universal_newlines
設置成True
,則子進程的stdout
和stderr
被視為文本對象,并且不管是unix/linux的換行符('\n'),還是老mac格式的換行符('\r'),還是windows 格式的換行符('\r\n')都將被視為'\n' 。
如果指定了startupinfo
和creationflags
,它們將會被傳遞給后面的CreateProcess()
函數,用于指定子程序的各種其他屬性,比如主窗口樣式或者是子進程的優先級等。(僅限Windows)
再解釋一下兩個我們后面要用到的東西:
subprocess.PIPE
一個可以用于Popen的stdin
、stdout
或stderr
參數的特殊值,它指示應打開到標準流的管道。
subprocess.STDOUT
一個可以被用于Popen的stderr
參數的特殊值,表示子程序的標準錯誤與標準輸出匯合到同一句柄。
現在回到我們將要解決的問題
已知:
-w
參數打包導致python無法處理輸入值(stdin)os.popen
打開的管道卻需要處理輸入值(stdin)所以,我們不使用os.popen
這個簡單的封裝,改成使用subprocess.popen
,接著將subprocess.popen
打開管道的輸入值(stdin)重定向,即可解決問題!
請看下列示例:
proc = subprocess.Popen( 'cmd命令', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE # 重定向輸入值 ) proc.stdin.close() # 既然沒有命令行窗口,那就關閉輸入 proc.wait() result = proc.stdout.read() # 讀取cmd執行的輸出結果(是byte類型,需要decode) proc.stdout.close()
這樣處理后我們用-w
參數打包就不會再報錯了!
也可以將輸出值(stdout)定向到文件輸出,請看:
with open('輸出文件.txt' , 'w+', encoding='utf-8') as out_file: proc = subprocess.Popen( 'cmd命令', shell=True, stdout=out_file, # 注意這里!變成了文件對象! stderr=subprocess.STDOUT, stdin=subprocess.PIPE ) ret = proc.wait() # 此處其實有返回值 with open('輸出文件.txt', 'r', encoding='utf-8' as read_file: output = read_file.read() # 這樣就得到cmd命令的輸出結果了
稍微封裝一下,就可以直接拿來用了
def execute_cmd(cmd): proc = subprocess.Popen( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE ) proc.stdin.close() proc.wait() result = proc.stdout.read().decode('gbk') # 注意你電腦cmd的輸出編碼(中文是gbk) proc.stdout.close() return result result = execute_cmd('taskkill /f /t /im nginx.exe') print(result)
舒服了!!!!
當然,實在要用輸入,又不想要控制臺怎么辦?很簡單,把控制臺隱藏了就行!
下列兩個方法,試試看:
import ctypes def hideConsole(): """ Hides the console window in GUI mode. Necessary for frozen application, because this application support both, command line processing AND GUI mode and theirfor cannot be run via pythonw.exe. """ whnd = ctypes.windll.kernel32.GetConsoleWindow() if whnd != 0: ctypes.windll.user32.ShowWindow(whnd, 0) # if you wanted to close the handles... #ctypes.windll.kernel32.CloseHandle(whnd) def showConsole(): """Unhides console window""" whnd = ctypes.windll.kernel32.GetConsoleWindow() if whnd != 0: ctypes.windll.user32.ShowWindow(whnd, 1)
看完這篇關于pyinstaller打包成無控制臺程序時運行出錯怎么辦的文章,如果覺得文章內容寫得不錯的話,可以把它分享出去給更多人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。