脚本编译后如何在python中重新加载模块?

Posted

技术标签:

【中文标题】脚本编译后如何在python中重新加载模块?【英文标题】:How to make a module reload in python after the script is compiled? 【发布时间】: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) 【参考方案1】:

问题总结:

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

有几种简单的解决方案。

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

【讨论】:

以上是关于脚本编译后如何在python中重新加载模块?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中,如何在重新加载后更改实例化对象?

在 Python 3.4 中重新加载模块 [重复]

如何使 VSCode 自动重新加载外部 *.py 模块?

在python中重新加载模块

python如何重新加载模块

如何在 Python 中重新加载模块的函数?