PyInstaller打包含speedtest_cli的Tkinter线程脚本时运行报错
问题:PyInstaller打包带threading的Tkinter测速脚本后运行报错
错误堆栈
Traceback (most recent call last): File "speedtest.py", line 156, in <module> ModuleNotFoundError: No module named '__builtin__' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "Speedtest.py", line 3, in <module> from speedtest_cli import Speedtest File "<frozen importlib._bootstrap>", line 1360, in _find_and_load File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 935, in _load_unlocked File "PyInstaller\loader\pyimod02_importers.py", line 450, in exec_module File "speedtest_cli.py", line 30, in <module> File "<frozen importlib._bootstrap>", line 1360, in _find_and_load File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 935, in _load_unlocked File "PyInstaller\loader\pyimod02_importers.py", line 450, in exec_module File "speedtest.py", line 179, in <module> File "speedtest.py", line 166, in __init__ AttributeError: 'NoneType' object has no attribute 'fileno'
使用的打包命令
process = subprocess.Popen( r'pyinstaller.exe --onefile --noconsole --distpath D:\PythonProjects\Speedtest Speedtest.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True) output, error = process.communicate() if error: print(error.decode()) if output: print(output.decode())
已尝试的方案
- 将
--noconsole替换为--windowed - 删除
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE
注:无法删除--noconsole,需要.exe运行时不显示控制台。
原测速脚本代码
from tkinter import * from tkinter import ttk from speedtest_cli import Speedtest import threading root = Tk() root["bg"] = "#fafafa" root.geometry("300x400") root.title("Speedtest") root.iconbitmap("icon.ico") root.resizable(False, False) style = ttk.Style() style.configure("TButton", font=("Comic Sans MS", 20)) def speedtest(): downloadText.config(text="Download Speed:\n-") uploadText.config(text="Upload Speed:\n-") pingText.config(text="Ping:\n-") st = Speedtest() errorText.place_forget() analyzingText.place(anchor=CENTER, relx=0.5, rely=0.92) downloadSpeed = round(st.download() / (10 ** 6), 2) uploadSpeed = round(st.upload() / (10 ** 6), 2) pingSpeed = st.results.ping downloadText.config(text="Download Speed:\n" + str(downloadSpeed) + " Mbps") uploadText.config(text="Upload Speed:\n" + str(uploadSpeed) + " Mbps") pingText.config(text="Ping:\n" + str(pingSpeed) + " Ms") analyzingText.place_forget() def speedtestChecked(): try: speedtest() except Exception: analyzingText.place_forget() errorText.place(anchor=CENTER, relx=0.5, rely=0.92) def startSpeedtestThread(): speedtestThread = threading.Thread(target=speedtestChecked) speedtestThread.start() speedtestButton = ttk.Button(root, text="Start Speedtest", style="TButton", command=startSpeedtestThread) speedtestButton.pack(side=BOTTOM, pady=60) analyzingText = Label(text="Analyzing...", bg="#fafafa", font=("Comic Sans MS", 17)) errorText = Label(text="Error occurred!", bg="#fafafa", font=("Comic Sans MS", 17), fg="#a83636") downloadText = Label(text="Download Speed:\n-", bg="#fafafa", font=("Comic Sans MS", 17)) uploadText = Label(text="Upload Speed:\n-", bg="#fafafa", font=("Comic Sans MS", 17)) pingText = Label(text="Ping\n-", bg="#fafafa", font=("Comic Sans MS", 17)) downloadText.place(anchor=CENTER, relx=0.5, rely=0.13) uploadText.place(anchor=CENTER, relx=0.5, rely=0.35) pingText.place(anchor=CENTER, relx=0.5, rely=0.57) root.mainloop()
解决方案
1. 重定向标准输入输出流
使用--noconsole参数时,Python的标准流(stdin/stdout/stderr)会被设置为None,而speedtest_cli内部会尝试调用这些流的fileno()方法,导致报错。在测速脚本的最开头添加以下代码,将标准流重定向到系统空设备:
import sys import os # 仅在打包后的环境中执行该逻辑 if getattr(sys, 'frozen', False): null_device = os.devnull sys.stdin = open(null_device, 'r') sys.stdout = open(null_device, 'w') sys.stderr = open(null_device, 'w')
2. 添加隐藏导入修复__builtin__错误
__builtin__是Python2的内置模块,Python3中替换为builtins,speedtest_cli的兼容代码导致PyInstaller未自动收集该依赖。修改打包命令,添加--hidden-import builtins:
pyinstaller.exe --onefile --noconsole --hidden-import builtins --distpath D:\PythonProjects\Speedtest Speedtest.py
如果用subprocess执行,对应修改代码:
process = subprocess.Popen( r'pyinstaller.exe --onefile --noconsole --hidden-import builtins --distpath D:\PythonProjects\Speedtest Speedtest.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
3. 组合两种方案
同时添加重定向代码和修改打包命令,确保完全解决两个错误。
内容的提问来源于stack exchange,提问作者Sword_3300
相关产品推荐
相关产品推荐

