Reputation: 11
I tried to compile a TKinter script into .exe file with pyinstall, but when I open the .exe file it shows an error. My TKinter script has threading. Here's a stack trace showing unhandled exception in speedtest.py
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'
Also here's the code I used for compilation:
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())
I tried:
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE
Deleting --noconsole is not an option, I don't need a console while in .exe file
Also here is speedtest.py
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()
Upvotes: 1
Views: 62
Reputation: 295696
Threading works fine -- your problem has nothing whatsoever to do with threading.
The problem is that the speedtest_cli
library tries to wrap stdout and stderr for UTF-8 compatibility (needed because it tries to be compatible with ancient Python 2.x releases where Unicode strings weren't default), and the code it uses for that purpose doesn't work in a GUI when one doesn't have a real terminal handle: the NullWriter
object set up by pyinstaller with --noconsole
doesn't provide the fileno()
method that a file object wrapping a real file descriptor offers.
Specifically, the code in question:
class _Py3Utf8Output(TextIOWrapper):
"""UTF-8 encoded wrapper around stdout for py3, to override
ASCII stdout
"""
def __init__(self, f, **kwargs):
buf = FileIO(f.fileno(), 'w')
super(_Py3Utf8Output, self).__init__(
buf,
encoding='utf8',
errors='strict'
)
def write(self, s):
super(_Py3Utf8Output, self).write(s)
self.flush()
The code applying that patch (in lines shortly below those quoted) aborts if an OSError
is encountered, but it doesn't have the same fallback logic for an AttributeError
.
If there's no non-CLI-specific alternative library, I'd patch it to rip that functionality out entirely; you don't need it, and it's providing no value. You could also just change the exception handling to handle AttributeError
or Exception
as a whole.
Upvotes: 0
Reputation: 1
The error AttributeError: 'NoneType' object has no attribute 'fileno'
when compiling with py installer
occur due to how threading and certain libraries interact when creating standalone executables.
These might help
Handle 'stdout' and 'stderr' Remove the 'stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE' options in the 'subprocess.Popen()' call. Redirecting these can sometimes cause issues in GUI apps when compiled.
Add --hidden-import
for Missing Modules
speedtest
or other libraries may dynamically import certain modules, which PyInstaller fails to detect.
Ensure Proper Path for External Resources Ensure that 'icon.ico' is accessible when the program is run as an executable. You might need to specify the relative path or package it using PyInstaller:
Use a Spec File for Advanced Configuration
Upvotes: -1