1. 程式人生 > 程式設計 >pyinstaller打包找不到檔案的問題解決

pyinstaller打包找不到檔案的問題解決

1、將python程式打包成單檔案(使用 -F 引數)後,嘗試執行外部檔案卻提示找不到的問題

當你將python程式打包成單檔案(使用 -F 引數)後,執行程式,它實際上是先將exe內的資原始檔解壓到臨時資料夾,然後再執行的,所以會導致這種問題

比如,當你在程式裡面呼叫一個外部exe時,但卻提示找不到該exe檔案。

例子(這裡我用win32api去隱式執行外部exe檔案):

import win32api
win32api.ShellExecute(0,'open','nginx.exe','',0)

首先,你需要將這個外部的exe檔案新增進pyinstaller的打包裡。

有兩種方法:

1、直接用引數新增:

--add-data "nginx.exe;."

完整命令:pyinstaller -F main.py --add-data "nginx.exe;."

2、在spec檔案新增:

每次執行pyinstaller打包命令後會生成spec檔案,開啟它
在裡面找到data=[] 列表,新增元素,變成了:datas=[('nginx.exe','.')]
然後用spec打包:pyinstaller main.spec
我解釋一下這個點“.”是什麼意思:

由於使用單檔案打包出來的exe會先解壓再執行,所以點“.” 其實表示你打包的這個exe檔案執行解壓的完整路徑

如:C:\...\temp(臨時資料夾)\asdqwezxc(你程式執行時自動解壓到的目錄)

  • 所以這個nginx.exe 被打包後,會解壓到 C:\...\temp\asdqwezxc\nginx.exe
  • 如果把點“.”改為test,就會解壓到 C:\...\temp\asdqwezxc\test\nginx.exe
  • 以此類推

好,現在nginx.exe已被打包。然後要注意一個問題:

打包出來的exe在執行時,它的工作路徑和它解壓到的路徑,是不一樣的!

你可以測試一下:

import os
print(os.getcwd())

可以發現,打印出來的工作路徑並不是它執行時解壓到的路徑!
而是這個打包出來的exe,它本身所存在的路徑!

問題來了:

諸如open('xxx.txt')這些操作檔案的函式,一般首先都是在工作路徑查詢你所指定的檔案的。

所以,當我們直接這樣執行已打包的外部檔案時,程式會報找不到檔案!所以請使用它的解壓路徑。

下面提供一個函式,可以很方便的獲取到解壓路徑:

import os,sys
def base_path(path):
  if getattr(sys,'frozen',None):
    basedir = sys._MEIPASS
  else:
    basedir = os.path.dirname(__file__)
  return os.path.join(basedir,path)

print(base_path(''))
print(base_path('test\gg.exe'))

第一句列印會顯示完整的解壓路徑:

C:\...\temp\asdqwezxc\

第二句列印是這樣的:

C:\...\temp\asdqwezxc\test\gg.exe

所以當我們在呼叫已打包的外部檔案時,應該先使用os.chdir()將工作路徑改為解壓路徑:

再進行操作,就不會報檔案找不到了

os.chdir(base_path(''))
win32api.ShellExecute(0,0)

不過要注意的是,如果你要寫出檔案到程式所在的目錄(非解壓目錄),那麼你得把工作目錄改回來,否則檔案會被寫出到解壓路徑(臨時資料夾)。

稍微封裝一下就好了:

import os,sys

def base_path(path):
  if getattr(sys,path)

tmd = base_path('') # 這是解壓路徑
cwd = os.getcwd() # 這是程式的所在路徑

# 當需要呼叫打包的外部檔案時
os.chdir(tmd) # 先把工作路徑變成解壓路徑
do() # 執行你要乾的事情

# 當需要寫出檔案到程式所在目錄時
os.chdir(cwd) # 把工作路徑切換回來
do() # 執行你要乾的事情

2、當你使用cython將py檔案編譯成pyd檔案後使用pyinstaller打包,提示找不到模組的問題
直接使用pyinstaller打包py檔案是很容易導致原始碼被反編譯的

所以在打包的時候最好將py檔案編譯成pyd檔案,這樣可以很大程度上防止反編譯。

為什麼呢?因為pyd檔案的來歷是這樣的:

py檔案 → c檔案 → pyd檔案
所以直接反編譯pyd只能得到上一步cython生成的c檔案,而無法得到我們的py原始檔。

如何打包pyd成檔案請看這篇文章:https://www.jb51.net/article/184725.htm
接下來回到我們的問題。

解決方法很簡單,請看:

比如說,我有一個檔案main.py,引入了位於同級目錄下的test.py模組

# main.py:就像這樣直接引入
import test

現在我將test.py 編譯成pyd檔案,生成了:test.cp37-win_amd64.pyd

這個pyd檔名除了我們原本的檔名test,還會帶上編譯環境的名稱,這個環境字尾名我們可以不用管 ,因為python引入模組還是很智慧的(會自動引入.pyd檔案,因為它的優先順序高於.py檔案)。

這麼智慧,但是為什麼我用pyinstaller打包時就提示找不到檔案?
其實我們需要在打包時--hidden-import這些模組

1、直接新增

在打包時新增--hidden-import test即可
完整命令:pyinstaller -F --hidden-import test

2、使用spec檔案

同樣的,執行一次pyinstaller打包命令後會生成spec檔案,開啟它
找到hiddenimports=[],新增test模組,變成了:hiddenimports=['test']
很簡單對吧?
而且除了我們自己寫的一些py模組,其它模組在打包時可能也會提示找不到,都可以用這個方法解決。

3、打包成單檔案時(使用-F引數),執行時要求管理員許可權的引數--uac-admin無效的問題
請看我的這篇文章:pyinstaller打包單檔案時–uac-admin選項不起作用怎麼辦

本質上大概也是因為找不到檔案。

4、無控制檯打包(使用-w引數),執行時彈框提示Failed to execute script的問題
請看我的這篇文章:pyinstaller打包成無控制檯程式時執行出錯,與popen衝突的解決方法

這個問題一般是程式內有輸入導致的,這個輸入可以是input(),也可以是其它的一些stdin操作(如os.popen實際上會造成輸入請求)

本質上就是:使用-w引數(無控制檯)打包時程式裡不要請求輸入

當然,實在要用輸入,又不想要控制檯怎麼辦?很簡單,把控制檯隱藏了就行!

下列兩個方法,試試看:

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打包找不到檔案的問題解決的文章就介紹到這了,更多相關pyinstaller打包找不到檔案內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!