如何通过单击按钮重新执行我的Python程序

时间:2016-02-24 22:45:33

标签: python python-3.x

我有一个程序,我正在无休止地编辑,每次我做一个更改,我必须退出并重新启动。我希望能够重新加载并重新启动,而不必退出并再次返回。

这是一个名为reload.py的文件中的一个简单程序,如果有可用的reexec,它会做我想要的:

from tkinter import *

class Application(Frame):
    def do_load(self):
        print("time to reload")
        reexec("reload.py")

    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.my_reload = Button(self)
        self.my_reload["text"] = "Reload",
        self.my_reload["command"] = self.do_load
        self.my_reload.pack()


if __name__ == '__main__':
    root = Tk()
    app = Application(master=root)
    app.mainloop()

我尝试使用以下方法重新加载Application类:

def do_load(self):
    print("time to reload")
    reload(Application)

我收到错误:

File "python3.4/importlib/__init__.py", line 122, in reload
    raise TypeError("reload() argument must be module")
TypeError: reload() argument must be module

这是一个RTFM案例,我需要了解更多有关模块的内容吗?

任何帮助或建议都会很乐意接受。

2 个答案:

答案 0 :(得分:3)

为了在这里使用<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Private Area</title> </head> <body> <h1>Home</h1> <h2>Welcome <?php echo $username; ?>!</h2> <a href="home/logout">Logout</a> <div> <button type="button" class="btn">Load Book</button> </div> </body> </html> ,你必须重新加载主模块,并杀死TK实例,这样python就可以用编辑过的源代码重新创建另一个模块。

<强> main.py

importlib

<强> executor.py

from tkinter import *


class Application(Frame):
    def __init__(self, master=None, on_reload=None):
        Frame.__init__(self, master)
        self.pack()
        self.my_reload = Button(self)
        self.my_reload["text"] = "Reload",
        self.my_reload["command"] = on_reload
        self.my_reload.pack()

答案 1 :(得分:0)

我刚刚完成了我正在创建的工作项目的确切问题。 Gal Ben David提供的答案确实有效,但仅适用于具有单个窗口的应用程序。这是因为tkinter.Tk根是在应用程序初始化时创建的,而不是在脚本底部的条件语句下创建的。因此,user1683793支持将其放置在正确的位置。只要root是全局变量,您就可以轻松地使用它生成具有相同根对象的其他窗口。

以下面的项目根目录为例。

    root/
     ├── __main__.py
     └── myapp/
          ├── __init__.py
          ├── core.py
          ├── datatypes.py
          └── gui.py

忽略__init__.py软件包的myapp文件,您有两个模块 您的应用程序的用户界面:保存主窗口的myapp.core模块和保存核心容器使用的所有组件的myapp.gui模块。看到更改类意味着更改模块本身,重新加载整个模块更有意义,因此始终在运行时使用最新版本。

要在不重新启动程序的情况下重新加载应用程序GUI,使用的过程必须:

  1. 重新加载已编辑的模块(通常使用importlib.reload
  2. 使用旧对象中的信息从新模块的内容中构建新对象。
    • 请确保将所有其他对象/属性的所有权转让给新对象。
  3. 取消引用旧对象以进行垃圾回收。这包括旧模块以及与之相关的所有类和功能。

root / myapp / core.py

首先,您需要一个与其他GUI组件完全隔离的容器类。最好开始并能够返回到空的默认状态,这是在编辑root/myapp/gui.py后尝试重新加载其组件时将保留的状态。此默认状态将仅保留菜单栏,所有重新加载命令都存储在运行时菜单中。

from tkinter import *

#Import commented out until live editing has been completed
#from myapp import gui, datatypes

class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master=master)

        self.build_vars() #Builds all Tk variables used by this frame
        self.build_menus() #Builds Application Menus

        self.container = Frame(self) #Container for all Application Components 
        self.build_components()  #Builds GUI Components
        ...

    def build_menus(self):
        """
        Builds the application menus bar.
        """
        self.menubar = Menu(self) #Menu Bar

        filemenu = Menu(self.menubar, tearoff=0) #File Menu
        self.menubar.add_cascade(label="File", menu=filemenu) #Adds File Menu to Menu Bar

        runtimemenu = Menu(self.menubar, tearoff=0) #Runtime Menu - Location of All 
        runtimemenu.add_command(label="Reload Window", command=self.reload_window)
        runtimemenu.add_command(label="Reload Components", command=self.reload_components)
        runtimemenu.add_separator()
        runtimemenu.add_command(label="Reload Datatypes", command=self.reload_datatypes)
        self.menubar.add_cascade(label="Runtime", menu=runtimemenu)

        self.master.config(menu=self.menubar)

    def build_components(self):
        """
        Builds the application components.
        """
        #Keep all imports to reloadable dependencies in 
        #the local scope to use the most recent version
        from myapp.gui import AppFrame

        self.pane = AppFrame(self.container)
        self.pane.pack()
        ...
        self.container.pack()

    def build_vars(self):
        """
        Build all Tk variables (BooleanVar, DoubleVar, IntVar, StringVar) used by the application.
        """
        ...

    def reload_window(self):
        """
        Reloads the entire Application frame.

        Use when making minor changes to this class to update the Application instance at runtime.
        """
        from myapp import core
        from importlib import reload
        core = reload(core) #Reload the current module to reflect any changes
        cls = getattr(app, type(self).__name__) #Get Application from reloaded module
        frame = cls(self.master)
        self.destroy() #Destroy instance of the old application before packing the new instance
        frame.pack()

    def reload_components(self):
        """
        Reloads all components used in the Application frame.

        Use when making changes to the classes of GUI components to 
        update the application GUI at runtime.
        """
        from myapp import gui
        from importlib import reload
        reload(gui) #Reload the current module to reflect any changes
        #Destroy the old GUI Components
        for widget in self.container.winfo_children():
            widget.destroy()
        self.build_components() #Rebuild the GUI components using the reloaded module

    def reload_datatypes(self):
        """
        Reloads a user module named `datatypes` .

        This command is useful for reflecting major and 
        minor changes to the target module at runtime.
        """
        from myapp import datatypes
        from importlib import reload
        reload(datatypes)

reload_windowreload_components方法分别重新加载应用程序的核心模块和组件模块。

reload_window,然后在重新加载的myapp.core中搜索与当前应用程序类同名的类,从新类中创建一个新对象,并销毁旧的应用程序窗口。

reload_components从基础容器中删除所有组件,然后重新运行build_components。由于import语句是在build_components中声明的,因此它将始终使用最新版本的重载模块。

root / myapp / gui.py

此模块将包含myapp.core模块使用的所有GUI组件。

from tkinter import *

class AppFrame(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master=master)
        ...

root / __ main __。py

最后,在目录顶部使用__main__.py,您可以运行应用程序并重新加载应用程序的特定部分,而不必完全重新启动程序。明确引用的唯一用户代码是启动时的myapp.core.Application

from tkinter import Tk
#Module Setup Code
...

def create_application(master):
    #Keep all imports to reloadable dependencies in 
    #the local scope to use the most recent version
    from myapp.core import Application
    return Application(master=root)

if __name__ == '__main__':
    root = Tk()
    app = create_application(root)
    app.mainloop()

注意:为使此操作尽可能有效,应将所有导入保留到重新加载的模块中,这些模块本地化为使用它们的功能,直到完成编辑这些模块为止。取决于您使用的加载程序,以这种方式重新加载模块多次(如果是全局导入的话)可能会使同一模块的多个版本在整个程序中徘徊。在本地导入时,除sys.modules中的模块之外,没有保留对模块的有效引用。重新加载期间替换的所有未引用模块都可以用于垃圾回收。

如果您无法做到这一点,一种解决方法是包括一个函数force_update(self, module),该函数搜索旧模块版本中对内容的引用,并使用映射到最新模块的引用创建一个新对象模块的版本。为此,甚至不必寻找旧模块。检查功能是否最新可以很简单。

def force_update(self, module):
    func = self.attr_func
    same = func.__module__ == module.__name__
    newfunc = getattr(module, func.__name__)
    if same and func != newfunc:
        self.attr_func = newfunc
    return self