脚本编译后如何在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中重新加载模块?的主要内容,如果未能解决你的问题,请参考以下文章