为什么我会收到EOFError错误?

3

我正在使用 tkinter 和 urllib 制作一个类似于下载管理器的程序。在接近完成程序时,我意识到我没有为下载定义取消按钮。在进一步研究后,我发现了 multiprocessing(之前我只使用了线程),它是一个比线程更好的模块,用于并行运行函数,并且还具有终止功能。然而,无论我怎么做,我似乎都无法理解这个模块。它比线程复杂得多,而且我经常遇到荒谬的错误。在我的程序中,我经常会遇到以下错误:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "C:/Users/Family/PycharmProjects/8-bit Downloader/test.py", line 258, in start_download
Process(target=download_files,
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\process.py", line 121, in start
    self._popen = self._Popen(self)
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\context.py", line 327, in _Popen
    return Popen(process_obj)
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\popen_spawn_win32.py", line 93, in __init__
reduction.dump(process_obj, to_child)
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
TypeError: cannot pickle '_tkinter.tkapp' object
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "C:\Users\Family\AppData\Local\Programs\Python\Python38-32\lib\multiprocessing\spawn.py", line 126, in _main
    self = reduction.pickle.load(from_parent)
EOFError: Ran out of input

我完全不知道是什么导致了这个错误以及如何修复它。似乎 start_download() 函数存在问题。以下是我的代码(我知道代码有一些缺陷,我可以在完成基础部分后将其清理干净,如果您认为某些部分与主题无关,请告诉我。):

import os
import sqlite3
import time
import urllib.request
from multiprocessing import Process
from pathlib import Path
from tkinter import *
from tkinter import filedialog
from tkinter import font
from tkinter import messagebox
from tkinter import ttk
import numpy as np
import requests
import win10toast
from PIL import Image, ImageTk

files_downloading, times_clicked, dir_files = 0, 0, {}
info_window, customize = None, None


# The problem I believe is with the two functions below and the start function some of the code is related to the downloads tab
# which is irrelevant and some other were just functions that were unrelated so if you see a button without a function that
# exists or it is 'None' it is just deleted for the purpose of this question and also ignore the image variables!

def get_info(path, new_fullname, url_entry):
    global times_clicked
    lbl_await.pack_forget()
    prg_br.pack_forget()
    file_size = int(requests.head(str(url_entry.get()),
                                  headers={'accept-encoding': ''}).headers['Content-Length']) / 1000000
    print(file_size)
    file_dir = Path(str(path).replace(" \ ".strip(), "/") + "/" + new_fullname)
    size_eachsec = np.array([0.0, ])
    my_scrollbar.pack(side=RIGHT, fill=Y)
    lbl_fr = ttk.LabelFrame(second_frame, text=new_fullname, padding=5)
    if len(new_fullname) > 20:
        lbl_fr.config(text=new_fullname[:21] + "...")
    download_prg = ttk.Progressbar(lbl_fr, orient=HORIZONTAL, length=365, mode='determinate', cursor="wait")
    lbl_speed = Label(lbl_fr, text=None)
    lbl_crnt_size = Label(lbl_fr, text=None)
    lbl_file_size = Label(lbl_fr, text=f"File size: {round(file_size, 3)} MB")
    lbl_percent = Label(lbl_fr, text="0 %")
    lbl_cancel_btn = ttk.Button(lbl_fr, image=cancel_btn_icon)
    lbl_see_more = Label(second_frame, text="See more", fg="grey", font=("Arial", 10))
    download_prg.grid(row=0, column=0, columnspan=2)
    lbl_speed.grid(row=0, column=2, padx=5)
    lbl_crnt_size.grid(row=1, column=1)
    lbl_file_size.grid(row=1, column=0)
    lbl_percent.grid(row=1, column=2)
    lbl_cancel_btn.grid(row=0, column=3, rowspan=2)
    lbl_fr.grid(row=times_clicked, column=0, padx=5, pady=5)
    lbl_see_more.grid(row=times_clicked, column=1, padx=3)
    times_clicked += 1
    start_time = time.time()
    while True:
        if file_dir.exists():
            time.sleep(0.5)
            crnt_size = file_dir.stat().st_size / 1000000
            size_eachsec = np.append(size_eachsec, crnt_size)
            percent = (crnt_size / file_size) * 100
            crnt_speed = size_eachsec[1] - size_eachsec[0]
            size_eachsec = np.delete(size_eachsec, 0)
            lbl_speed.config(text=f"{round(crnt_speed, 2)} MB/s")
            lbl_crnt_size.config(text=f"Downloaded: {round(crnt_size, 3)} MB")
            lbl_percent.config(text=f"{round(percent, 2)} %")
            download_prg["value"] = percent
            # print( f"Current Size: {crnt_size}, Current speed: {crnt_speed}, File size: {file_size},
            # Percentage: {percent}% " f", Progress bar: {download_prg['value']}%")
            if crnt_size == file_size:
                break
    end_time = time.time()
    time_elapsed = end_time - start_time
    print(f"Start time: {start_time}, End time: {end_time}, Time took: {time_elapsed}")
    download_prg.config(cursor="arrow")
    lbl_speed.config(text="Completed!")

    def show_info(event):
        global info_window
        if info_window is not None and info_window.winfo_exists():
            info_window.focus_set()
            lbl_see_more.config(fg="purple")
        else:
            lbl_see_more.config(fg="purple")
            info_window = Toplevel()
            info_window.resizable(0, 0)
            lbl_time_took = Label(info_window, text=f"Time elapsed: {round(time_elapsed, 2)}")
            lbl_avg_speed = Label(info_window, text=f"Average speed: {round(file_size / time_elapsed, 3)} MB/s")
            lbl_time_took.pack(pady=20)
            lbl_avg_speed.pack(pady=20)

    lbl_see_more.bind("<Button-1>", show_info)
    new_font = font.Font(lbl_see_more, lbl_see_more.cget("font"))
    new_font.configure(size=10, underline=True)
    lbl_see_more.config(font=new_font, fg="blue", cursor="hand2")
    print("Download successful! ")


def download_files(status_bar, url_entry, output_entry, name_entry, format_entry, num, chum, var):
    global files_downloading
    files_downloading += 1
    status_bar.config(text=f"Downloading {files_downloading} file(s)...")
    url = str(url_entry.get())

    if num.get() == 1:
        name = url.split("/")[-1].split(".")[0]
    else:
        name = str(name_entry.get())

    formatname = str(format_entry.get())
    if var.get() == 1:
        operator = str(url_entry.get())
        formatname = '.' + operator[-3] + operator[-2] + operator[-1]
    else:
        pass

    static_fullname = str(name) + formatname
    new_fullname = static_fullname
    path = (str(output_entry.get()) + "/").replace(" \ ".strip(), "/")
    if chum.get() == 1:
        conn = sqlite3.connect("DEF_PATH.db")
        c = conn.cursor()
        c.execute("SELECT * FROM DIRECTORY_LIST WHERE SELECTED_DEF = 1")
        crnt_default_path = c.fetchall()
        # print(crnt_default_path)
        path = str(crnt_default_path[0][0] + "/").replace(" \ ".strip(), "/")
        conn.commit()
        conn.close()
    else:
        pass

    if path + static_fullname not in dir_files.keys():
        dir_files[path + static_fullname] = 0

    all_files_dir = os.listdir(path)
    # if fullname in all_files_dir:
    while new_fullname in all_files_dir:
        dir_files[path + static_fullname] += 1
        if num.get() == 1:
            name = url.split("/")[-1].split(".")[0] + f" ({dir_files.get(path + static_fullname)})"
        else:
            name = str(name_entry.get()) + f" ({dir_files.get(path + static_fullname)})"
        new_fullname = name + formatname
    else:
        pass
    print(dir_files)

    Process(target=get_info, args=(path, new_fullname, url_entry)).start()
    urllib.request.urlretrieve(url, path + new_fullname)
    if len(new_fullname) > 20:
        toast.show_toast(title="8-bit Downloader",
                         msg=f"{new_fullname[:21] + '...'} was successfully downloaded. to '{path}'"
                         , duration=6, threaded=True)
    else:
        toast.show_toast(title="8-bit Downloader", msg=f"{new_fullname} was successfully downloaded. to '{path}'"
                         , duration=6, threaded=True)
    files_downloading -= 1
    status_bar.config(text=f"Downloading {files_downloading} file(s)...")
    if files_downloading == 0:
        status_bar.config(text="Download(s) successful!")


if __name__ == '__main__':
    root = Tk()
    tabs = ttk.Notebook()
    # the top menu
    num = IntVar()
    chum = IntVar()
    var = IntVar()
    toast = win10toast.ToastNotifier()
    
    def start_download():
        Process(target=download_files,
                args=(status_bar, url_entry, output_entry, name_entry, format_entry, num, chum, var),
                daemon=True).start()


    # the status bar
    status_bar = Label(main_window, text="Awaiting download...", bd=1, relief=SUNKEN, anchor=W)
    status_bar.pack(side=BOTTOM, fill=X)

    # the download frame
    body_frame = Frame(main_window, bg="light blue")
    download_button = ttk.Button(body_frame, text="Download! ", command=start_download, padding=40, style='my.TButton')
    download_button_tip = CreateToolTip(download_button, "Initiates the downloading process. ")
    download_button.pack(side=LEFT, pady=5, padx=5)
    body_frame.pack(side=LEFT, fill=Y)


    def clear_entry(entryname, variable=None):
        if variable is not None:
            variable.set(0)
        entryname.config(state=NORMAL)
        entryname.delete(0, END)
        if entryname is format_entry:
            entryname.insert(0, '.')


    # the main interaction menu
    inter_frame = Frame(main_window)
    trash_icon = ImageTk.PhotoImage(Image.open("icons/Asset 6.png"))
    settings_icon = ImageTk.PhotoImage(Image.open("icons/Settings-icon.png"))
    add_icon = ImageTk.PhotoImage(Image.open("icons/Plus-icon-8.png"))
    minus_icon = ImageTk.PhotoImage(Image.open("icons/Minus-icon-8.png"))
    change_path_icon = ImageTk.PhotoImage(Image.open("icons/Change-icon-8.png"))
    url_entry = ttk.Entry(inter_frame, width=30)
    label = ttk.Label(inter_frame, text="Enter the image URL: ")
    clr_url = ttk.Button(inter_frame, image=trash_icon, command=lambda: clear_entry(url_entry))
    file_format = ttk.Label(inter_frame, text="Choose your file format: ")
    format_entry = ttk.Entry(inter_frame, width=30)
    clr_format = ttk.Button(inter_frame, image=trash_icon, command=lambda: clear_entry(format_entry, var))
    file_name = ttk.Label(inter_frame, text="File's name: ")
    name_entry = ttk.Entry(inter_frame, width=30)
    clr_name = ttk.Button(inter_frame, image=trash_icon, command=lambda: clear_entry(name_entry, num))
    check_name_manual = ttk.Radiobutton(inter_frame, text="Enter name manually", variable=num,
                                        value=0, command=lambda: name_entry.config(state=NORMAL))
    check_name_auto = ttk.Radiobutton(inter_frame, text="Download with default name", variable=num,
                                      value=1, command=lambda: name_entry.config(state=DISABLED))
    check_format_manual = ttk.Radiobutton(inter_frame, text="Enter format manually", variable=var,
                                          value=0, command=lambda: format_entry.config(state=NORMAL))
    check_format_auto = ttk.Radiobutton(inter_frame, text="Download with default format", variable=var,
                                        value=1, command=lambda: format_entry.config(state=DISABLED))
    output_path = ttk.Label(inter_frame, text="Choose output path: ")
    output_entry = ttk.Entry(inter_frame, width=30)


    def set_path():
        directory = filedialog.askdirectory(initialdir="/Downloads", title="Choose path")
        if directory:
            output_entry.delete(0, END)
            output_entry.insert(0, directory)


    select_path_btn = ttk.Button(inter_frame, width=3, text="...", command=set_path)
    select_path_btn_tip = CreateToolTip(select_path_btn, "Pops up a filedialog to change directory. ")


    default_path_btn = ttk.Button(inter_frame, image=settings_icon, command=None)
    check_default_manual = ttk.Radiobutton(inter_frame, text="Enter path manually", variable=chum,
                                           value=0, command=lambda: output_entry.config(state=NORMAL))
    check_default_auto = ttk.Radiobutton(inter_frame, text="Download to default path", variable=chum,
                                         value=1, command=lambda: output_entry.config(state=DISABLED))
    file_name.grid(row=0, column=0, pady=(15, 10))
    name_entry.grid(row=0, column=1, pady=(15, 10))
    clr_name.grid(row=0, column=2, pady=(5, 0))
    check_name_manual.grid(row=1, column=0, padx=(10, 0))
    check_name_auto.grid(row=1, column=1, padx=(10, 0))
    label.grid(row=2, column=0, pady=10, padx=(10, 0))
    url_entry.grid(row=2, column=1, pady=10, padx=(10, 0))
    clr_url.grid(row=2, column=2)
    file_format.grid(row=3, column=0, pady=10, padx=(10, 0))
    format_entry.grid(row=3, column=1, pady=10, padx=(10, 0))
    format_entry.insert(0, '.')
    clr_format.grid(row=3, column=2)
    check_format_manual.grid(row=4, column=0, padx=(10, 0))
    check_format_auto.grid(row=4, column=1, padx=(10, 0))
    output_path.grid(row=5, column=0, pady=10, padx=(10, 0))
    output_entry.grid(row=5, column=1, padx=(10, 5))
    select_path_btn.grid(row=5, column=2)
    check_default_manual.grid(row=6, column=0, pady=(0, 10), padx=(10, 0))
    check_default_auto.grid(row=6, column=1, pady=(0, 10), padx=(10, 0))
    default_path_btn.grid(row=6, column=2, pady=(0, 5))
    inter_frame.pack(expand=1)
    main_window.pack(fill="both", expand=1)
    # The Downloads tab (this part is irrelevant so I wouldn't bother checking this part out just put it there in case)
    lbl_await = ttk.Label(download_fr, text="Awaiting Download...", font=("Helvetica", 24))
    prg_br = ttk.Progressbar(download_fr, orient=HORIZONTAL, length=250, mode='indeterminate')
    my_canvas = Canvas(download_fr)
    my_scrollbar = ttk.Scrollbar(download_fr, orient=VERTICAL, command=my_canvas.yview)
    my_canvas.configure(yscrollcommand=my_scrollbar.set)
    lbl_await.pack(pady=(85, 8))
    prg_br.pack()
    prg_br.start(6)
    my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
    download_fr.pack(fill="both", expand=1)
    second_frame = Frame(my_canvas)
    # Add that new frame to a window in the canvas
    my_canvas.create_window(0, 0, window=second_frame, anchor='nw')
    # update canvas scrollregion whenever the size of second_frame is changed
    second_frame.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox('all')))
    tabs.pack(fill="both")
    tabs.add(main_window, text="Main Window")
    tabs.add(download_fr, text="Downloads")
    root.mainloop()

# the end!
1个回答

0

我认为你的主要问题是这个:

TypeError: cannot pickle '_tkinter.tkapp' object

看起来你正在尝试在进程之间传递Tk对象。我怀疑这将永远不会成功。


我明白,但是我尝试运行这些进程而没有传递任何参数,但是我收到了一个错误消息,上面写着“Nameerror: 'status_bar'未定义”,之后我尝试将小部件设置为全局变量,但仍然出现相同的错误。我不知道该怎么办。 - Omid Ketabollahi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接