iT邦幫忙

3

如何使用PyInstaller打包Python代碼為執行檔

上週五好不容易用Python內建的Tkinter將操作介面拉好且測試OK,沒想到在打包成執行檔(.exe)這邊花了一個上午,所以紀錄一下自己的碰壁流程。

工作環境:
-Windows 10, Windows Server 2019
-Anaconda 2020.02
-Python 3.6.10

問題描述:

  • 如何用PyInstaller打包Python代碼為執行檔(.exe)
  • 打包過程碰上的問題以及如何排除
  1. 先安裝PyInstaller:
pip install pyinstaller
  1. 打開Anaconda Prompt (anaconda3),並且將當前位置切至.py檔存放路徑:
cd C:\Users\*\Documents\GUI-20200323
  1. 輸入打包代碼,我要打包的是main.py。一般的教程都只到這邊就停了,但後面才蛋疼:
pyinstaller main.py

4. 問題:RecursionError: maximum recursion depth exceeded

  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 253, in visit
    return visitor(node)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 263, in generic_visit
    self.visit(value)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 253, in visit
    return visitor(node)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 263, in generic_visit
    self.visit(value)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 253, in visit
    return visitor(node)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 263, in generic_visit
    self.visit(value)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 253, in visit
    return visitor(node)
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\ast.py", line 257, in generic_visit
    for field, value in iter_fields(node):
RecursionError: maximum recursion depth exceeded

突然蹦出這些有的沒的,解決方法很簡單,看一下.py檔存放路徑是不是出現了main.spec這個由PyInstaller自動產生的配置文件,用筆記本打開後新增下面兩行代碼後儲存離開,然後保險起見將build和dist兩個資料夾刪除:
原因在於超過遞迴限制,我也看不懂這什麼意思。[8]

# -*- mode: python ; coding: utf-8 -*-
import sys #我是新增的
sys.setrecursionlimit(9000000) #我是新增的,這邊數字越大越好
block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\*\\Documents\\GUI-20200323'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
  1. 執行配置文件main.spec進行打包
    解決上個問題後我們輸入下列代碼來重新製作執行檔,這次我們是要執行自訂的配置文件.spec,PyInstaller會遵循.spec指定規範進行打包:
pyinstaller main.spec

6-1. 閃退問題以及如何查詢原因(pkg_resources.py2_warn)

等了一會兒後終於跑完了:

113390 INFO: checking EXE
113390 INFO: Building EXE because EXE-00.toc is non existent
113391 INFO: Building EXE from EXE-00.toc
113391 INFO: Appending archive to EXE C:\Users\*\Documents\GUI-20200323\build\main\main.exe
113486 INFO: Building EXE from EXE-00.toc completed successfully.
113501 INFO: checking COLLECT
113502 INFO: Building COLLECT because COLLECT-00.toc is non existent
113502 INFO: Building COLLECT COLLECT-00.toc
184733 INFO: Building COLLECT COLLECT-00.toc completed successfully.

我們來看看main.exe是不是可以正常地打開吧,main.exe的位置在:

C:\Users\*\Documents\GUI-20200323\dist\main\main.exe

但是等等,為什麼會閃退!!!原來打包成功並不等於.exe可以順利執行呀。既然這樣那我們改用終端機來開啟.exe,這樣就能知道程是閃退的原因了[1]:

(keras4) C:\Users\*\Documents\GUI-20200323>cd C:\Users\*\Documents\GUI-20200323\dist\main #切換路徑到main.exe的位置
(keras4) C:\Users\*\Documents\GUI-20200323\dist\main>main.exe #執行
Traceback (most recent call last):
  File "site-packages\PyInstaller\loader\rthooks\pyi_rth_pkgres.py", line 13, in <module>
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\pkg_resources\__init__.py", line 86, in <module>
ModuleNotFoundError: No module named 'pkg_resources.py2_warn'# 這個是問題來源
[15208] Failed to execute script pyi_rth_pkgres
(keras4) C:\Users\*\Documents\GUI-20200323\dist\main>

原來是因為PyInstaller再打包過程中漏掉pkg_resources.py2_warn導致產生的.exe無法順利執行。這時我們再次開啟配置文件.spec,手動添加pkg_resources.py2_warn吧,記得再次執行main.spec前要先刪掉build、dist兩個資料夾[2][3][4]:

# -*- mode: python ; coding: utf-8 -*-
import sys
sys.setrecursionlimit(9000000)
block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\*\\Documents\\GUI-20200323'],
             binaries=[],
             datas=[],
             hiddenimports=['pkg_resources.py2_warn'], #我是新增的
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyinstaller main.spec

6-2. 閃退問題以及如何查詢原因(sklearn.utils._cython_blas)

現在我們再次執行.exe,結果又閃退了...,沒關係我們用上面講的方法,透過終端機來執行.exe檔就能知道問題了:

(keras4) C:\Users\*\Documents\GUI-20200323>cd C:\Users\*\Documents\GUI-20200323\dist\main #切換路徑到main.exe的位置
(keras4) C:\Users\*\Documents\GUI-20200323\dist\main>main.exe # 執行
Traceback (most recent call last):
  File "main.py", line 3, in <module>
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "mylib.py", line 10, in <module>
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\sklearn\linear_model\__init__.py", line 12, in <module>
  File "c:\users\*\appdata\local\continuum\anaconda3\envs\keras4\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\sklearn\linear_model\_least_angle.py", line 22, in <module>
  File "sklearn\utils\arrayfuncs.pyx", line 1, in init sklearn.utils.arrayfuncs
ModuleNotFoundError: No module named 'sklearn.utils._cython_blas'# 這個是問題來源
[11656] Failed to execute script main

(keras4) C:\Users\*\Documents\GUI-20200323\dist\main>

這次是打包過程中漏掉sklearn.utils._cython_blas,PyInstaller你這個小淘氣~:

# -*- mode: python ; coding: utf-8 -*-
import sys
sys.setrecursionlimit(9000000)
block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\*\\Documents\\GUI-20200323'],
             binaries=[],
             datas=[],
             hiddenimports=['pkg_resources.py2_warn', 'sklearn.utils._cython_blas'], # 再加1個
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
  1. 收工
    根據孟母三遷原理,我們終於順利打包完成並且可以執行main.exe了(灑花)。然後你的螢幕會彈出辛苦拉的視窗以及一個黑色畫面,消除黑色畫面的方法很簡單,只要依照下面代碼位置方式修改.spec檔後再次打包即可。
    成果
a = Analysis(['main.py'],
             pathex=['C:\\Users\\*\\Documents\\GUI-20200323'],
             binaries=[],
             datas=[],
             hiddenimports=['pkg_resources.py2_warn', 'sklearn.utils._cython_blas'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=False # console要從True改成False)

結論:

  • 打包完成不等於可以順利運行.exe檔
  • PyInstaller的打包方式是由.spec定義
  • 我們可以自定義.spec
  • 如果有引用第三方套件(Numpy、Pandas等等),容易有漏打包問題,導致執行檔有閃退問題
  • 用終端機執行.exe來確認閃退原因
  • 每次自動打包前都要刪掉資料夾build、資料夾dist、配置文件.spec,以確保遺毒消除
  • 每次手動打包前都要刪掉資料夾build、資料夾dist,以確保遺毒消除
  • 補一下最後的main.spec代碼,可以拿來跟下一篇比較
# -*- mode: python ; coding: utf-8 -*-
import sys # 這邊要注意
sys.setrecursionlimit(9000000) # 這邊要注意
block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\e10832\\Documents\\GUI-20200323'],
             binaries=[],
             datas=[],
             hiddenimports=['pkg_resources.py2_warn', 'sklearn.utils._cython_blas'], # 這邊要注意
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )# 命令視窗開啟(True)
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='main')

參考資料:
[1]https://blog.csdn.net/weixin_41417982/article/details/82216363
[2]https://blog.csdn.net/qq_40587575/article/details/86500445
[3]https://blog.csdn.net/j754379117/article/details/77281354
[4]https://medium.com/@peaceful0907/%E5%B0%87%E4%BD%A0%E7%9A%84python-code-%E6%89%93%E5%8C%85-pyinstaller-6777d0e06f58
[5]https://zhuanlan.zhihu.com/p/58199926
[6]https://zhuanlan.zhihu.com/p/40716095
[7]https://zhuanlan.zhihu.com/p/76974787
[8]https://xken831.pixnet.net/blog/post/463075799-%5Bpython%5D-%E8%A7%A3%E6%B1%BA-recursionerror%3A-maximum-recursion-depth-exce


尚未有邦友留言

立即登入留言