Многопоточность с графическим интерфейсом Tkinter в Python возвращает ошибку «RecursionError: превышена максимальная глубина рекурсии при вызове объекта Python» - PullRequest
0 голосов
/ 21 октября 2019

Я пишу программу резервного копирования файлов, похожую на Microsoft Synctoy. Он берет исходную папку и папку назначения, а затем проверяет, находятся ли файлы из источника в месте назначения;если они есть, ничего не делайте, если нет, скопируйте файлы. Это основная идея.

Я использовал библиотеку Tkinter для Python, чтобы написать GUI для программы, принимая ввод от пользователя с помощью виджетов Entry, а затем запуская функцию копирования, когда пользователь нажимает кнопку «копировать». ,Под этим на моем GUI находится индикатор выполнения, который я обновляю каждые 100 мс. Это работает путем нахождения размера уже скопированных файлов и деления его на общий размер всех файлов в предоставленной исходной директории. Это дает мне процентное значение завершения, после чего я устанавливаю значения индикатора выполнения.

Проблема, с которой я столкнулся, заключалась в том, что когда файлы копировались программой, графический интерфейс переходил в состояние отсутствия ответа. Чтобы исправить это, я попытался использовать многопоточность (у меня очень ограниченное понимание того, как это использовать). Проблема, с которой я столкнулся сейчас, заключается в том, что когда я нажимаю кнопку «Копировать», она сбрасывает индикатор выполнения на 0, как и должно, затем создает отдельный поток, который выполняет функцию копирования, а затем запускает функцию обновления индикатора выполнения, которая вызывает себя с помощью Tkinter. после функции, но возвращает ошибку «RecursionError: максимальная глубина рекурсии превышена при вызове объекта Python». Я полагаю, что это связано с тем, что функция обновления индикатора выполнения имеет проблему с методом .after и многопоточностью.

Основной файл

import Copy_file
import tkinter as tk
from tkinter import ttk
from PIL import ImageTk, Image
import threading

# setup window
window = tk.Tk()
window.title("Simple File Backup")
window.resizable(False, False)
window.iconbitmap("Images\\Icon.ico")

class main_window:
    def __init__(self, root):
        # initialise other program
        self.copy = Copy_file.copy_file()

        # configure styles
        self.s = ttk.Style()
        self.s.configure("TButton", font=("Humanst521 BT", 15))
        self.s.configure("main.TLabel", font=("Humanst521 BT", 10))
        self.s.configure("title.TLabel", font=("Humanst521 BT", 19))

        # create frames
        self.main_frame = ttk.Frame(root, padding=10)
        self.main_frame.grid()

        self.top_frame = ttk.Frame(self.main_frame)
        self.top_frame.grid(row=0)

        self.extra_frame = ttk.Frame(self.main_frame)
        self.extra_frame.grid(row=1)

        # add logo
        self.logo = Image.open("Images\\Icon.png")
        self.logo = self.logo.resize((50, 50), Image.ANTIALIAS)
        self.logo = ImageTk.PhotoImage(self.logo)
        self.show_logo = tk.Label(self.top_frame, image=self.logo, relief="sunken")
        self.show_logo.image = self.logo
        self.show_logo.grid(row=0, column=1, sticky="e")

        # title
        ttk.Label(self.top_frame, text="Simple File Backup", style="title.TLabel").grid(row=0, column=0, columnspan=2, sticky="w")

        # create main layout
        ttk.Label(self.top_frame, text="Source Directory: ", style="main.TLabel").grid(row=1, column=0, pady=5)

        self.source_directory = ttk.Entry(self.top_frame, width=30)
        self.source_directory.grid(row=1, column=1)

        ttk.Label(self.top_frame, text="Destination Directory: ", style="main.TLabel").grid(row=2, column=0)

        self.destination_directory = ttk.Entry(self.top_frame, width=30)
        self.destination_directory.grid(row=2, column=1)

        # copy button
        self.copy_button = ttk.Button(self.top_frame, text="Copy", style="TButton", command=lambda:self.start())
        self.copy_button.grid(row=3, column=0, columnspan=2, pady=5)

        # extra items layout
        self.progress_bar = ttk.Progressbar(self.extra_frame, orient="horizontal", length=300, value=0)
        self.progress_bar.grid(row=0, column=0, columnspan=2)

        self.output_box = tk.Text(self.extra_frame, takefocus=0, width=40, height=3)
        self.output_box.grid(row=1, column=0, columnspan=2, pady=5, padx=5)

    def start(self):
        self.reset_progressbar()
        self.update_progressbar()
        self.thread = threading.Thread(target=self.copy.main_copy, args=(self.get_contents(self.source_directory), self.get_contents(self.destination_directory)))
        self.thread.start()

    def reset_progressbar(self):
        self.progress_bar["value"] = 0

    def update_progressbar(self):
        if not self.progress_bar["value"] == 100.0:
            self.progress_bar["value"] = self.copy.current_progress()
            print(self.copy.current_progress())
            window.after(100, self.update_progressbar())

    def get_contents(self, object):
        self.output = object.get()
        return self.output


# run main_window class with root at tkinter window
main_window(window)

# main gui loop
tk.mainloop()

Файл_копии

import os
import shutil

class copy_file():
    def __init__(self):
        self.file_bytes_copied = 0
        self.full_directory_size = 0

    def get_directory_size(self, directory):
        self.total_size = 0
        for self.dir, self.subdir, self.file in os.walk(directory):
            for self.x in range(len(self.file)):
                self.total_size += os.stat(self.dir + "\\" + self.file[self.x]).st_size
        return self.total_size

    def current_progress(self):
        if self.file_bytes_copied > 0 and self.full_directory_size > 0:
            self.progress = (self.file_bytes_copied/self.full_directory_size)*100
            self.progress = round(self.progress, 2)
            return self.progress

    def concatenate_file_modification(self, path):
        self.name = os.path.basename(path)
        self.name = self.name.split(".")
        self.mod_time = str(os.stat(path).st_mtime)
        self.mod_time = self.mod_time.replace(".", "-")
        self.concatenate = self.name[0]+" "+self.mod_time+"."+self.name[1]
        return self.concatenate

    def main_copy(self, source, destination):
        self.full_directory_size = self.get_directory_size(source)

        self.file_bytes_copied = 0.01
        for self.root_dir, self.sub_dir, self.file in os.walk(source):
            # remove the first part of the source, leaving the relative directory of the files within the source
            self.root_dir = self.root_dir[len(source):]

            # create current directory variable
            self.current_directory = destination+self.root_dir

            # iterate through sub-directories
            for i in range(len(self.sub_dir)):
                # check if directory exists, if not create it
                if not os.path.isdir(self.current_directory+"\\"+self.sub_dir[i]):
                    os.mkdir(self.current_directory+"\\"+self.sub_dir[i])
                    print(self.current_directory+"\\"+self.sub_dir[i], "wasn't found in destination, created directory")

            # iterate through files
            for self.i in range(len(self.file)):
                # get the file directory to search for
                self.file_to_search = self.current_directory+"\\"+self.concatenate_file_modification(source+self.root_dir+"\\"+self.file[self.i])

                # check if path exists, if not copy across file
                if not os.path.exists(self.file_to_search):
                    self.test = shutil.copy2((source + self.root_dir + "\\" + self.file[self.i]), self.file_to_search)
                    print(self.test)
                    print(self.file_to_search+" wasn't found in destination, created copy")
                # if file does exist, print no action taken
                elif os.path.exists(self.file_to_search):
                    print(self.file_to_search+" was found in destination, no action taken")

                print("ok")
                # update file_bytes_copied to current progress
                self.file_bytes_copied += os.stat(source + self.root_dir + "\\" + self.file[self.i]).st_size
...