【问题标题】:How to make a module reload in python after the script is compiled?脚本编译后如何在python中重新加载模块?
【发布时间】:2021-03-11 16:37:59
【问题描述】:

涉及的基本思想:

我正在尝试制作一个学生可以编写代码的应用程序 与特定问题相关(比如检查数字是否为偶数) 然后由应用程序检查学生给出的代码 将用户代码给出的输出与正确的输出进行比较 由应用程序中已经存在的正确代码给出。

我正在做的项目的基本版本:

您可以在其中编写 python 脚本(在 tkinter 文本中)的应用程序 盒子)。文本框的内容首先存储在一个test_it.py 文件。该文件然后由 应用。然后调用test_it.py 中的函数 获取代码的输出(由用户)。

问题:

由于我正在“导入” test_it.py 的内容,因此, 在应用程序运行期间,用户可以测试他的脚本 只有一次。原因是python会导入test_it.py 只归档一次。因此,即使将用户的新脚本保存在 test_it.py ,它不会对应用程序可用。

解决办法:

Reloadtest_it.py每次点击测试脚本的按钮时。

实际问题:

虽然当我从脚本运行应用程序时,这可以完美运行, 此方法不适用于文件的已编译/可执行版本(.exe)(这是预期的,因为在编译期间所有导入的模块都将是 也编译过,所以以后修改它们将不起作用)

问题:

我希望我的test_it.py 文件即使在编译应用程序后也能重新加载。


如果您想查看应用程序的工作版本以自行测试。你会发现它here

【问题讨论】:

  • 您可以使用-B command line flag 启动应用程序以防止创建字节码,如果这就是您所说的“文件的编译版本”
  • @MauriceMeyer 通过编译,我的意思是制作一个.exe。对困惑感到抱歉。我不懂很多术语
  • 你用pyinstaller创建.exe?
  • @viilpe 是的。不完全是 pyinstaller,而是 this(在其后端使用 pyinstaller)

标签: python python-3.x module reload


【解决方案1】:

问题总结:

test_it.py 程序正在运行并且有一个可用的谓词,例如is_odd()。 每隔几分钟,一个包含修改后的is_odd() 谓词的新写入文件就会变得可用, 并且 test_it 希望为修改后的谓词提供一个测试向量。

有几种简单的解决方案。

  1. 不要在当前进程中加载​​谓词。序列化测试向量,将其发送到计算和序列化结果的newly forked child,然后检查这些结果。
  2. 通常eval 是邪恶的,但在这里你可能想要那个,或者执行。
  3. 用新初始化的解释器替换当前进程:https://docs.python.org/3/library/os.html#os.execl
  4. 走内存泄漏路线。使用计数器为每个新文件分配一个唯一的模块名称,操作源文件以匹配,并加载 that。作为奖励,这可以很容易地将当前结果与以前的结果进行比较。
  5. Reload: from importlib import reload

【讨论】:

  • 我没有尝试过(除了第 5 个),但我对第一个有疑问。当使用subprocess.Popen() 或同等工具时,应用程序是否会在未安装 python 3 的系统中运行(将应用程序制成 .exe 后)?
  • 想要在缺少 python 解释器的系统上运行任意 *.py 源代码,嗯,这是一个不寻常的设计要求。我引用的 .check_output() 例程非常简单——它将 fork + exec 任何你想要的程序,例如/usr/bin/date,并将它发送到标准输出的内容返回。我记得你可能想要运行一个接受一些源代码作为输入的 python 解释器,因为你手头有一个新收到的源文件。但是我想如果你真的想你可以安排其他接口,比如要求代码提交者在他的端编译到x86。 numba.pydata.org
【解决方案2】:

即使是捆绑的应用程序导入也以标准方式工作。这意味着每当遇到import 时,解释器都会尝试查找相应的模块。您可以通过将包含目录附加到sys.path 来使您的test_it.py 模块可被发现。 import test_it 应该是动态的,例如在函数内部,这样它就不会被 PyInstaller 发现(这样 PyInstaller 就不会尝试将它与应用程序捆绑在一起)。

考虑以下示例脚本,其中应用数据存储在托管test_it.py 模块的临时目录中:

import importlib
import os
import sys
import tempfile

def main():
    with tempfile.TemporaryDirectory() as td:
        f_name = os.path.join(td, 'test_it.py')

        with open(f_name, 'w') as fh:  # write the code
            fh.write('foo = 1')

        sys.path.append(td)  # make available for import
        import test_it
        print(f'{test_it.foo=}')

        with open(f_name, 'w') as fh:  # update the code
            fh.write('foo = 2')

        importlib.reload(test_it)
        print(f'{test_it.foo=}')

main()

【讨论】:

    【解决方案3】:

    关键是检查程序是否作为exe运行并将exe路径添加到sys.path

    文件program.py

    import time
    import sys
    import os
    import msvcrt
    import importlib
    
    if getattr(sys, 'frozen', False):
        # This is .exe so we change current working dir
        # to the exe file directory:
        app_path = os.path.dirname(sys.executable)
        print('    Add .exe path to sys.path: ' + app_path)
        sys.path.append(app_path)
        os.chdir(app_path)
    
    test_it = importlib.import_module('test_it')
    
    def main():
        global test_it
        try:
            print('    Start')
            while True:
                if not msvcrt.kbhit(): continue
                key = msvcrt.getch()
                if key in b'rR':
                    print('    Reload module')
                    del sys.modules['test_it']
                    del test_it
                    test_it = importlib.import_module('test_it')
                elif key in b'tT':
                    print('    Run test')
                    test_it.test_func()
                time.sleep(0.001)
        except KeyboardInterrupt:
            print('    Exit')
    
    if __name__ == '__main__': main()
    

    文件test_it.py

    def test_func():
        print('Hi')
    

    创建一个 .exe 文件:

    pyinstaller --onefile  --clean program.py
    

    将 _text_it.py 复制到 _dist_ 文件夹即可。 在程序窗口中按 t 运行test_func。编辑 test_it.py 然后按 r 重新加载模块并再次按 t 以查看更改。

    【讨论】:

      【解决方案4】:

      也许解决方案是使用code module

      import code
      # get source from file as a string
      src_code = ''.join(open('test_it.py').readlines())
      # compile the source
      compiled_code = code.compile_command(source=src_code, symbol='exec')
      # run the code
      eval(compiled_code) # or exec(compiled_code)
      

      【讨论】:

        猜你喜欢
        • 2019-01-27
        • 2012-02-15
        • 2011-09-08
        • 2017-04-14
        • 2010-10-01
        • 2012-10-19
        相关资源
        最近更新 更多