编译脚本后如何在 python 中重新加载模块?
How to make a module reload in python after the script is compiled?
涉及的基本思想:
我正在尝试制作一个学生可以编写代码的应用程序
与特定问题相关(比如检查数字是否为偶数)
然后由应用程序检查学生给出的代码
将用户代码给出的输出与正确的输出进行比较
由应用程序中已存在的正确代码给出。
我正在做的项目的基础版本:
一个应用程序,您可以在其中编写 python 脚本(在 tkinter 文本中
盒子)。文本框的内容首先存储在一个test_it.py
文件。然后这个文件被导入(点击一个按钮)
应用。然后调用 test_it.py
中的函数
获取代码的输出(由用户)。
问题:
由于我正在“导入”test_it.py
的内容,因此,
在应用程序的 运行 时间内,用户可以测试他的脚本
只有一次。原因是 python 将导入 test_it.py
只归档一次。因此,即使在将用户的新脚本保存在
test_it.py ,应用程序将无法使用它。
解决方法:
Reload test_it.py
每次单击测试脚本的按钮时。
实际问题:
虽然当我从脚本 运行 应用程序时这完美地工作,
此方法不适用于文件的 compiled/executable 版本 (.exe)(这是预期的,因为在编译期间所有导入的模块将被
编译过所以以后修改它们将不起作用)
问题:
我希望我的 test_it.py
文件即使在编译应用程序后也能重新加载。
如果您想查看应用程序的工作版本,请自行测试。你会发现它 here.
问题总结:
一个 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
关键是检查程序是否运行宁为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_文件夹即可。
在程序window中按t到运行test_func
。编辑 test_it.py 然后按 r 重新加载模块并再次按 t 查看更改。
也许解决方案是使用 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)
即使是捆绑应用程序导入也以标准方式工作。这意味着每当遇到 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()
涉及的基本思想:
我正在尝试制作一个学生可以编写代码的应用程序 与特定问题相关(比如检查数字是否为偶数) 然后由应用程序检查学生给出的代码 将用户代码给出的输出与正确的输出进行比较 由应用程序中已存在的正确代码给出。
我正在做的项目的基础版本:
一个应用程序,您可以在其中编写 python 脚本(在 tkinter 文本中
盒子)。文本框的内容首先存储在一个test_it.py
文件。然后这个文件被导入(点击一个按钮)
应用。然后调用 test_it.py
中的函数
获取代码的输出(由用户)。
问题:
由于我正在“导入”test_it.py
的内容,因此,
在应用程序的 运行 时间内,用户可以测试他的脚本
只有一次。原因是 python 将导入 test_it.py
只归档一次。因此,即使在将用户的新脚本保存在
test_it.py ,应用程序将无法使用它。
解决方法:
Reload test_it.py
每次单击测试脚本的按钮时。
实际问题:
虽然当我从脚本 运行 应用程序时这完美地工作, 此方法不适用于文件的 compiled/executable 版本 (.exe)(这是预期的,因为在编译期间所有导入的模块将被 编译过所以以后修改它们将不起作用)
问题:
我希望我的 test_it.py
文件即使在编译应用程序后也能重新加载。
如果您想查看应用程序的工作版本,请自行测试。你会发现它 here.
问题总结:
一个 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
关键是检查程序是否运行宁为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_文件夹即可。
在程序window中按t到运行test_func
。编辑 test_it.py 然后按 r 重新加载模块并再次按 t 查看更改。
也许解决方案是使用 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)
即使是捆绑应用程序导入也以标准方式工作。这意味着每当遇到 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()