戰(zhàn):PyInstaller與Nuitka從腳本到可執(zhí)行文件)
簡(jiǎn)介這是一款面向Python開(kāi)發(fā)者的圖形化打包輔助工具針對(duì)源碼分發(fā)時(shí)目標(biāo)機(jī)器缺少Python環(huán)境、命令行參數(shù)繁瑣等痛點(diǎn)將PyInstaller的常用能力封裝為可視化界面。使用者只需填寫(xiě)腳本路徑、選擇單文件或單目錄模式、指定輸出位置即可完成從依賴(lài)分析到可執(zhí)行文件生成的全過(guò)程適合剛接觸打包的新手及需要快速交付程序的中級(jí)開(kāi)發(fā)者。資源包共416個(gè)文件以202個(gè)css樣式、152個(gè)png界面截圖、27個(gè)js腳本為主另含少量pyc、py源碼、xml配置與說(shuō)明文檔整體約747KB體積輕巧便于本地留存查閱。其中界面截圖直觀展示操作流程與打包結(jié)果說(shuō)明文檔則覆蓋使用方法、注意事項(xiàng)與常見(jiàn)問(wèn)題解答。目前已有2455人學(xué)習(xí)下載可幫助讀者快速理解PyInstaller的捆綁機(jī)制與依賴(lài)收集思路掌握跨平臺(tái)分發(fā)Python程序的實(shí)用技能。1. 從一堆散亂腳本到可交付程序Python 打包工具到底在解決什么你寫(xiě)完了一個(gè) Python 小工具在自己電腦上跑得好好的發(fā)給同事卻直接報(bào)錯(cuò)——不是缺模塊就是路徑不對(duì)要么干脆雙擊沒(méi)反應(yīng)。這種場(chǎng)景幾乎每個(gè)用 Python 做過(guò)交付的人都遇到過(guò)。Python 打包工具要解決的核心問(wèn)題就一個(gè)把源碼、解釋器、依賴(lài)庫(kù)和資源文件揉成一個(gè)對(duì)方不需要裝 Python 就能直接運(yùn)行的東西。常見(jiàn)路線有 PyInstaller、cx_Freeze、Nuitka 這幾條其中 PyInstaller 因?yàn)樯鲜挚?、兼容性好是多?shù)人第一次打包時(shí)的選擇。這篇筆記圍繞一份 Python 打包工具資源包展開(kāi)拆清楚它包含什么、怎么用、參數(shù)怎么調(diào)以及那些官方文檔不會(huì)告訴你的翻車(chē)點(diǎn)。適合已經(jīng)能寫(xiě) Python 腳本、但還沒(méi)把「交付」這一步走通的人。2. PyInstaller 打包機(jī)制與資源包結(jié)構(gòu)拆解2.1 打包到底做了什么從 .py 到可執(zhí)行文件的鏈路理解 PyInstaller 的工作方式比死記命令重要得多。它的流程大致分三步分析、收集、組裝。第一步是分析。PyInstaller 從你的入口腳本開(kāi)始順著 import 語(yǔ)句遞歸掃描構(gòu)建出一棵依賴(lài)樹(shù)。這里有個(gè)關(guān)鍵點(diǎn)它只能識(shí)別靜態(tài) import。如果你在代碼里用importlib.import_module()或者_(dá)_import__()動(dòng)態(tài)加載模塊分析階段是看不到的打包出來(lái)的程序運(yùn)行時(shí)就會(huì)報(bào) ModuleNotFoundError。這是新手最常踩的第一個(gè)坑。第二步是收集。分析完成后PyInstaller 把 Python 解釋器本身、所有被識(shí)別到的第三方庫(kù)、以及你通過(guò)參數(shù)指定的額外文件統(tǒng)統(tǒng)收集到一個(gè)工作目錄里。字節(jié)碼文件.pyc會(huì)被嵌入所以你的源碼不會(huì)以明文形式出現(xiàn)在最終產(chǎn)物里——但注意這不等于加密用工具反編譯 .pyc 仍然可以還原出大致邏輯。第三步是組裝。根據(jù)你選擇的模式PyInstaller 把所有東西塞進(jìn)一個(gè)可執(zhí)行文件onefile 模式或者放在一個(gè)文件夾里onedir 模式。onefile 模式本質(zhì)上是在可執(zhí)行文件尾部附加了一個(gè)壓縮包運(yùn)行時(shí)先解壓到系統(tǒng)臨時(shí)目錄再啟動(dòng)所以啟動(dòng)速度會(huì)比 onedir 慢而且臨時(shí)目錄的路徑在不同系統(tǒng)上不一樣這直接影響你寫(xiě)資源路徑的方式。資源包里通常會(huì)包含一份 spec 文件模板。spec 文件是 PyInstaller 的構(gòu)建配置本質(zhì)是一個(gè) Python 腳本PyInstaller 執(zhí)行它來(lái)獲取構(gòu)建參數(shù)。很多人一直用命令行參數(shù)打包遇到復(fù)雜需求就抓瞎其實(shí)直接改 spec 文件靈活得多。資源包里的 spec 模板一般已經(jīng)預(yù)置了 hiddenimports、datas、excludes 這幾個(gè)關(guān)鍵字段的寫(xiě)法照著改就行。2.2 資源包目錄說(shuō)明與首次打包驗(yàn)證拿到資源包后先別急著打包自己的項(xiàng)目。建議先用一個(gè)最小示例跑通全流程確認(rèn)環(huán)境沒(méi)問(wèn)題。資源包的典型結(jié)構(gòu)如下python-pack-toolkit/ ├── examples/ │ ├── hello_app.py # 最小示例入口 │ └── assets/ │ └── config.json # 示例資源文件 ├── specs/ │ ├── basic.spec # 基礎(chǔ)打包配置 │ └── advanced.spec # 含資源文件和隱藏導(dǎo)入的配置 ├── scripts/ │ ├── build.sh # Linux/macOS 構(gòu)建腳本 │ └── build.bat # Windows 構(gòu)建腳本 ├── requirements.txt # 打包工具自身依賴(lài) └── README.md先安裝依賴(lài)再跑最小示例# 安裝打包工具鏈依賴(lài) pip install -r requirements.txt # 用基礎(chǔ) spec 打包最小示例 pyinstaller specs/basic.spec # 打包完成后產(chǎn)物在 dist/ 目錄下 # Windows 下是 dist/hello_app.exe # Linux/macOS 下是 dist/hello_app這里pyinstaller specs/basic.spec的意思是讓 PyInstaller 讀取 spec 文件來(lái)構(gòu)建而不是從命令行參數(shù)構(gòu)建。spec 文件里已經(jīng)寫(xiě)好了入口腳本路徑、輸出名稱(chēng)等。跑完之后去dist/目錄下雙擊或執(zhí)行產(chǎn)物如果能看到示例程序正常輸出說(shuō)明工具鏈沒(méi)問(wèn)題。驗(yàn)證時(shí)重點(diǎn)看兩件事一是程序能不能啟動(dòng)二是啟動(dòng)后有沒(méi)有報(bào)缺少模塊的錯(cuò)。如果報(bào)錯(cuò)把錯(cuò)誤信息完整記下來(lái)對(duì)照 2.3 節(jié)的排查思路處理。2.3 依賴(lài)分析與 hiddenimports 的配置邏輯PyInstaller 的依賴(lài)分析不是萬(wàn)能的。以下幾種情況它一定會(huì)漏動(dòng)態(tài)導(dǎo)入importlib.import_module(some_module)插件式加載通過(guò)配置文件或數(shù)據(jù)庫(kù)決定加載哪個(gè)模塊某些庫(kù)的 C 擴(kuò)展比如 PyQt 的某些子模塊、pandas 的可選后端漏掉的結(jié)果就是運(yùn)行時(shí)崩潰。解決辦法是在 spec 文件的hiddenimports列表里手動(dòng)補(bǔ)上# advanced.spec 片段 a Analysis( [../examples/hello_app.py], pathex[], binaries[], datas[(../examples/assets, assets)], # 把 assets 目錄打進(jìn)包 hiddenimports[ pkg_resources.py2_warn, # setuptools 相關(guān)常見(jiàn)缺失 sqlalchemy.sql.default_comparator, # ORM 常見(jiàn)缺失 encodings.gbk, # 中文編碼相關(guān) ], hookspath[], excludes[tkinter, matplotlib], # 排除用不到的大庫(kù)減小體積 ... )datas參數(shù)的格式是(源路徑, 包內(nèi)目標(biāo)路徑)。上面這行的意思是把項(xiàng)目里的assets目錄整個(gè)復(fù)制到打包產(chǎn)物的assets目錄下。運(yùn)行時(shí)你要用sys._MEIPASS來(lái)定位這個(gè)路徑import sys import os def get_resource_path(relative_path): 兼容開(kāi)發(fā)環(huán)境和 PyInstaller 打包后的資源路徑獲取 if hasattr(sys, _MEIPASS): # 打包后資源被解壓到 sys._MEIPASS 指向的臨時(shí)目錄 base_path sys._MEIPASS else: # 開(kāi)發(fā)環(huán)境下用當(dāng)前文件所在目錄 base_path os.path.dirname(os.path.abspath(__file__)) return os.path.join(base_path, relative_path) config_path get_resource_path(assets/config.json)sys._MEIPASS是 PyInstaller 運(yùn)行時(shí)注入的屬性指向 onefile 模式下解壓出來(lái)的臨時(shí)目錄。如果不做這個(gè)判斷開(kāi)發(fā)時(shí)能跑、打包后找不到文件這是第二個(gè)高頻翻車(chē)點(diǎn)。excludes列表用來(lái)排除確定用不到的庫(kù)。比如你的程序是純命令行工具那 tkinter、matplotlib 這些完全可以排掉產(chǎn)物體積能從幾百 MB 降到幾十 MB。但排除要謹(jǐn)慎排錯(cuò)了就是運(yùn)行時(shí)崩潰建議每次只排一兩個(gè)打包后完整測(cè)一遍。3. 從 spec 到可執(zhí)行文件完整打包流程與參數(shù)調(diào)優(yōu)3.1 用 spec 文件管理構(gòu)建配置命令行打包適合快速驗(yàn)證但一旦項(xiàng)目稍微復(fù)雜一點(diǎn)就該轉(zhuǎn)到 spec 文件。原因很簡(jiǎn)單命令行參數(shù)一長(zhǎng)串換臺(tái)機(jī)器就忘了當(dāng)時(shí)怎么敲的spec 文件是版本可控的跟著項(xiàng)目走。生成 spec 文件有兩種方式。一種是用pyi-makespec命令從入口腳本生成初始版本# 生成一個(gè)基礎(chǔ) spec 文件指定入口腳本和輸出名稱(chēng) pyi-makespec --name my_tool --onefile examples/hello_app.py另一種是直接拿資源包里specs/advanced.spec改。我一般傾向后者因?yàn)橘Y源包里的模板已經(jīng)把常見(jiàn)坑位標(biāo)注好了省得從頭踩。spec 文件的核心結(jié)構(gòu)就三塊Analysis、PYZ、EXE。Analysis 負(fù)責(zé)分析依賴(lài)PYZ 負(fù)責(zé)打包字節(jié)碼EXE 負(fù)責(zé)生成最終可執(zhí)行文件。你改得最多的就是 Analysis 里的參數(shù)和 EXE 里的 console/icon 設(shè)置。# 一個(gè)經(jīng)過(guò)調(diào)優(yōu)的 spec 文件示例 # -*- mode: python ; coding: utf-8 -*- block_cipher None a Analysis( [../examples/hello_app.py], pathex[../examples], # 額外搜索路徑 binaries[], # 需要額外打包的二進(jìn)制文件 datas[(../examples/assets, assets)], hiddenimports[encodings.gbk], hookspath[], hooksconfig{}, runtime_hooks[], excludes[tkinter, unittest, pydoc], win_no_prefer_redirectsFalse, win_private_assembliesFalse, cipherblock_cipher, noarchiveFalse, ) pyz PYZ(a.pure, a.zipped_data, cipherblock_cipher) exe EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], namemy_tool, debugFalse, # 設(shè)為 True 會(huì)輸出大量調(diào)試信息排查問(wèn)題時(shí)用 bootloader_ignore_signalsFalse, stripFalse, # Linux 下可設(shè)為 True 減小體積 upxTrue, # 用 UPX 壓縮能顯著減小體積 upx_exclude[], runtime_tmpdirNone, # 自定義臨時(shí)目錄一般不用改 consoleTrue, # 命令行程序保持 TrueGUI 程序設(shè) False disable_windowed_tracebackFalse, argv_emulationFalse, target_archNone, codesign_identityNone, entitlements_fileNone, icon../examples/assets/app.ico, # 圖標(biāo)文件路徑 )upxTrue這個(gè)參數(shù)值得單獨(dú)說(shuō)。UPX 是一個(gè)可執(zhí)行文件壓縮器PyInstaller 集成它來(lái)壓縮產(chǎn)物。開(kāi)了之后體積通常能減 30% 到 50%但代價(jià)是啟動(dòng)時(shí)多一步解壓而且某些殺毒軟件會(huì)對(duì) UPX 壓縮過(guò)的文件誤報(bào)。如果你的程序要分發(fā)給普通用戶建議先關(guān)掉 UPX 測(cè)一輪確認(rèn)沒(méi)有殺軟攔截再?zèng)Q定是否開(kāi)啟。consoleFalse是 GUI 程序必須設(shè)的否則會(huì)彈出一個(gè)黑色命令行窗口。但設(shè)了之后程序崩潰時(shí)你看不到任何錯(cuò)誤信息排查起來(lái)很痛苦。我的做法是開(kāi)發(fā)階段保持consoleTrue確認(rèn)穩(wěn)定后再改成False出正式包。3.2 體積優(yōu)化與啟動(dòng)速度的取舍打包產(chǎn)物動(dòng)輒一兩百 MB這是很多人對(duì) PyInstaller 最大的不滿。體積主要來(lái)自三塊Python 解釋器本身約 15-25 MB、第三方庫(kù)、以及 PyInstaller 的 bootloader。優(yōu)化體積的手段按性?xún)r(jià)比排序第一用excludes排除確定用不到的大庫(kù)。numpy、pandas、matplotlib、scipy 這幾個(gè)是體積大戶如果你的程序確實(shí)不用排掉能省很多。但要注意間接依賴(lài)——你用的某個(gè)庫(kù)可能內(nèi)部 import 了 numpy排掉就崩。第二用虛擬環(huán)境打包。這是最容易被忽視但效果最明顯的一招。如果你在全局環(huán)境里打包PyInstaller 會(huì)把全局環(huán)境里所有能掃到的庫(kù)都分析一遍哪怕你沒(méi)直接用。建一個(gè)干凈的虛擬環(huán)境只裝項(xiàng)目必需的依賴(lài)打包體積能直接降一半。# 創(chuàng)建干凈的虛擬環(huán)境 python -m venv build_env # 激活Windows build_env\Scripts\activate # 激活Linux/macOS source build_env/bin/activate # 只安裝項(xiàng)目必需的依賴(lài) pip install -r requirements.txt # 在這個(gè)干凈環(huán)境里打包 pyinstaller specs/advanced.spec第三開(kāi)啟 UPX 壓縮。前面說(shuō)過(guò)了注意殺軟誤報(bào)的問(wèn)題。第四onefile 和 onedir 的取舍。onefile 體積略小因?yàn)閴嚎s了但啟動(dòng)慢onedir 啟動(dòng)快但文件多。如果用戶能接受一個(gè)文件夾onedir 是更好的選擇。如果必須單文件分發(fā)那就接受啟動(dòng)時(shí)的幾秒等待。啟動(dòng)速度方面onefile 模式每次運(yùn)行都要解壓到臨時(shí)目錄程序越大解壓越慢。有個(gè)技巧是把runtime_tmpdir設(shè)到一個(gè)固定路徑這樣第二次啟動(dòng)時(shí)如果臨時(shí)文件還在可以跳過(guò)解壓。但這會(huì)帶來(lái)緩存不一致的風(fēng)險(xiǎn)程序更新后舊緩存可能導(dǎo)致奇怪的問(wèn)題所以我不太推薦。3.3 跨平臺(tái)打包的現(xiàn)實(shí)約束PyInstaller 不支持交叉編譯。也就是說(shuō)你在 Windows 上打不出 Linux 的可執(zhí)行文件在 Linux 上也打不出 Windows 的 .exe。這不是 PyInstaller 的缺陷而是所有打包工具的共同限制——因?yàn)橐虬鼘?duì)應(yīng)平臺(tái)的 Python 解釋器和二進(jìn)制依賴(lài)。實(shí)際做法是用虛擬機(jī)或容器。資源包的scripts/目錄下提供了build.sh和build.bat兩個(gè)腳本分別對(duì)應(yīng) Linux/macOS 和 Windows。如果你需要同時(shí)出三個(gè)平臺(tái)的包最省事的方案是用 GitHub Actions 的 matrix 構(gòu)建每個(gè)平臺(tái)跑各自的 runner。# GitHub Actions 矩陣構(gòu)建示例片段 strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.11 - run: pip install -r requirements.txt - run: pyinstaller specs/advanced.spec - uses: actions/upload-artifactv4 with: name: build-${{ matrix.os }} path: dist/macOS 上還有個(gè)額外問(wèn)題從 macOS 10.15 開(kāi)始未簽名的應(yīng)用會(huì)被 Gatekeeper 攔截。用戶第一次打開(kāi)會(huì)看到「無(wú)法驗(yàn)證開(kāi)發(fā)者」的提示。解決辦法是要么做代碼簽名需要蘋(píng)果開(kāi)發(fā)者賬號(hào)要么告訴用戶在「系統(tǒng)設(shè)置 → 隱私與安全性」里手動(dòng)放行。如果只是內(nèi)部使用后者就夠了。4. 打包避坑實(shí)錄五條血淚經(jīng)驗(yàn)4.1 坑一打包成功但運(yùn)行報(bào) ModuleNotFoundError現(xiàn)象打包過(guò)程沒(méi)有任何報(bào)錯(cuò)但運(yùn)行產(chǎn)物時(shí)閃退或提示找不到某個(gè)模塊。原因PyInstaller 的靜態(tài)分析沒(méi)有識(shí)別到動(dòng)態(tài)導(dǎo)入的模塊。常見(jiàn)于插件式架構(gòu)、ORM 框架、以及通過(guò)字符串拼接 import 的代碼。解決在 spec 文件的hiddenimports里手動(dòng)補(bǔ)上缺失的模塊名。怎么知道缺哪個(gè)用consoleTrue打包運(yùn)行時(shí)看錯(cuò)誤信息里提到的模塊名加進(jìn)去重新打包。如果模塊很多可以在代碼里加一段運(yùn)行時(shí)檢測(cè)把所有已加載的模塊打印出來(lái)對(duì)比。4.2 坑二資源文件路徑在打包后失效現(xiàn)象開(kāi)發(fā)時(shí)用open(assets/config.json)能正常讀取打包后報(bào) FileNotFoundError。原因onefile 模式下程序運(yùn)行時(shí)的工作目錄不是可執(zhí)行文件所在目錄而是系統(tǒng)臨時(shí)目錄。相對(duì)路徑自然找不到文件。解決統(tǒng)一用sys._MEIPASS拼接資源路徑封裝成get_resource_path()函數(shù)見(jiàn) 2.3 節(jié)。所有涉及文件讀取的地方都走這個(gè)函數(shù)不要用相對(duì)路徑。4.3 坑三殺毒軟件誤報(bào)可執(zhí)行文件為病毒現(xiàn)象打包出來(lái)的 .exe 在別人電腦上被 Windows Defender 或某安全軟件直接隔離。原因PyInstaller 的 bootloader 行為特征和某些惡意軟件相似加上 UPX 壓縮會(huì)進(jìn)一步觸發(fā)啟發(fā)式檢測(cè)。這不是你的代碼有問(wèn)題是打包方式的固有特征。解決第一關(guān)掉 UPX 重新打包試試第二如果還報(bào)考慮用代碼簽名證書(shū)給可執(zhí)行文件簽名第三實(shí)在不行換 Nuitka 打包它的產(chǎn)物特征和 PyInstaller 不同誤報(bào)率低一些。內(nèi)部使用時(shí)把產(chǎn)物加到殺軟白名單也行。4.4 坑四打包體積異常膨脹現(xiàn)象一個(gè)幾百行的腳本打包出來(lái) 300 MB。原因在全局 Python 環(huán)境里打包PyInstaller 把全局環(huán)境里所有庫(kù)都分析進(jìn)去了?;蛘吣硞€(gè)依賴(lài)庫(kù)間接引入了 numpy、scipy 這類(lèi)大塊頭。解決用干凈的虛擬環(huán)境打包見(jiàn) 3.2 節(jié)。如果體積還是大用pyinstaller --log-levelDEBUG看分析日志找出是哪個(gè)庫(kù)貢獻(xiàn)了最多體積然后決定是否能排除。4.5 坑五macOS 上打包后無(wú)法運(yùn)行現(xiàn)象在 macOS 上打包成功但雙擊產(chǎn)物提示「無(wú)法打開(kāi)因?yàn)闊o(wú)法驗(yàn)證開(kāi)發(fā)者」。原因macOS 的 Gatekeeper 安全機(jī)制攔截了未簽名的應(yīng)用。解決臨時(shí)方案是右鍵點(diǎn)擊應(yīng)用選「打開(kāi)」或在「系統(tǒng)設(shè)置 → 隱私與安全性」里點(diǎn)「仍要打開(kāi)」。正式方案是購(gòu)買(mǎi)蘋(píng)果開(kāi)發(fā)者賬號(hào)做代碼簽名和公證。如果只是自己用還可以在終端里執(zhí)行xattr -cr /path/to/app清除隔離屬性。5. 進(jìn)階技巧用 Nuitka 做編譯級(jí)打包與產(chǎn)物驗(yàn)證PyInstaller 本質(zhì)上是「打包」不是「編譯」。它把 .pyc 字節(jié)碼和解釋器塞在一起運(yùn)行時(shí)還是解釋執(zhí)行。Nuitka 走的是另一條路把 Python 代碼翻譯成 C 代碼再用 C 編譯器編譯成真正的機(jī)器碼。產(chǎn)物啟動(dòng)更快、反編譯難度更高但打包過(guò)程更復(fù)雜、對(duì)代碼的兼容性要求也更高。如果你對(duì)啟動(dòng)速度或代碼保護(hù)有更高要求值得試試 Nuitka。資源包里沒(méi)有直接包含 Nuitka 的配置但裝好之后用法不復(fù)雜# 安裝 Nuitka pip install nuitka # 基本編譯命令 # --standalone 表示生成獨(dú)立分發(fā)包 # --onefile 表示打包成單個(gè)文件 # --enable-plugintk-inter 如果用了 tkinter 需要加對(duì)應(yīng)插件 python -m nuitka \ --standalone \ --onefile \ --enable-plugintk-inter \ --output-dirdist_nuitka \ examples/hello_app.pyNuitka 的--standalone相當(dāng)于 PyInstaller 的 onedir 模式--onefile相當(dāng)于 onefile 模式。它同樣需要指定隱藏導(dǎo)入和資源文件但參數(shù)名不同--include-module對(duì)應(yīng) hiddenimports--include-data-files對(duì)應(yīng) datas。Nuitka 的坑和 PyInstaller 不太一樣。它對(duì) Python 版本和 C 編譯器版本有要求Windows 上需要裝 MSVC 或 MinGWLinux 上需要 gcc。編譯時(shí)間也比 PyInstaller 長(zhǎng)得多一個(gè)小項(xiàng)目可能就要幾分鐘。另外某些用了大量元編程技巧的庫(kù)比如某些 ORM 和序列化框架在 Nuitka 下可能編譯失敗或運(yùn)行異常需要逐個(gè)排查。不管用哪個(gè)工具打包完成后都建議做一輪產(chǎn)物驗(yàn)證。我一般會(huì)走這個(gè)清單驗(yàn)證項(xiàng)操作方法通過(guò)標(biāo)準(zhǔn)啟動(dòng)測(cè)試在干凈機(jī)器上雙擊運(yùn)行正常啟動(dòng)無(wú)報(bào)錯(cuò)彈窗功能測(cè)試走一遍核心業(yè)務(wù)流程所有功能正常資源讀取檢查配置文件、圖標(biāo)等是否正常加載無(wú) FileNotFoundError退出測(cè)試正常關(guān)閉程序進(jìn)程完全退出無(wú)殘留體積檢查查看產(chǎn)物大小在預(yù)期范圍內(nèi)殺軟掃描用目標(biāo)機(jī)器上的殺軟掃一遍無(wú)報(bào)警這套流程走下來(lái)基本能覆蓋 90% 的交付問(wèn)題。從那以后我每次出正式包之前都會(huì)在至少一臺(tái)沒(méi)裝過(guò) Python 的干凈機(jī)器上完整跑一遍這個(gè)清單——因?yàn)殚_(kāi)發(fā)機(jī)上什么都有很多問(wèn)題只有在干凈環(huán)境里才會(huì)暴露。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取