You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 11:30:54