我有一个python程序,它在运行之前加载了大量数据。因此,我希望能够在不重新加载数据的情况下重新加载代码。对于常规python,importlib。重新加载工作正常。下面是一个示例:
设置。py:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = [
Extension("foo.bar", ["foo/bar.pyx"],
language="c++",
extra_compile_args=["-std=c++11"],
extra_link_args=["-std=c++11"])
]
setup(
name="system2",
ext_modules=cythonize(extensions, compiler_directives={'language_level' : "3"}),
)
食物/酒吧。py公司
cpdef say_hello():
print('Hello!')
runner.py:
import pyximport
pyximport.install(reload_support=True)
import foo.bar
import subprocess
from importlib import reload
if __name__ == '__main__':
def reload_bar():
p = subprocess.Popen('python setup.py build_ext --inplace',
shell=True,
cwd='<your directory>')
p.wait()
reload(foo.bar)
foo.bar.say_hello()
但这似乎不起作用。如果我编辑栏。pyx并运行
reload\u bar我看不到我的更改。我还尝试了pyximport。build\u module()运气不好--模块已重建,但没有重新加载。我在一个“普通”的python shell中运行,如果有什么不同的话,就不是IPython。
Cython扩展不是常见的python模块,因此底层操作系统的行为会有所改变。这个答案是关于Linux的,但其他操作系统也有类似的行为/问题(好吧,Windows甚至不允许您重建扩展)。
cython扩展是一个共享对象。导入时,CPython通过ldopen打开此共享对象,并调用init函数,即PyInit_
如果加载了共享对象,我们就不能再卸载它,因为可能有一些Python对象处于活动状态,这些对象将具有悬空指针,而不是指向原始共享对象的功能的函数指针。例如,请参阅此CPython问题。
另一件重要的事:当加载与已加载共享对象路径相同的共享对象时,它不会从光盘中读取该对象,而只会重用已加载的版本,即使光盘上有不同的版本。
这就是我们的方法的问题:只要得到的共享对象与旧对象具有相同的名称,如果不重新启动解释器,就永远无法在解释器中看到新功能。
你有什么选择?
A: 将pyximport与reload\u support=True一起使用
让我们假设您的Cython(foo.pyx)模块如下所示:
def doit():
print(42)
# called when loaded:
doit()
现在使用pyximport导入它:
>>> import pyximport
>>> pyximport.install(reload_support=True)
>>> import foo
42
>>> foo.doit()
42
foo.pyx
构建并加载(我们可以看到,它在加载时打印42,正如预期的那样)。让我们看一下foo
的文件:
>>> foo.__file__
'/home/XXX/.pyxbld/lib.linux-x86_64-3.6/foo.cpython-36m-x86_64-linux-gnu.so.reload1'
您可以看到与使用reload\u support=False构建的案例相比,额外的reload1后缀。看到文件名,我们还验证没有其他的foo。因此,在路径的某个地方,被错误加载。
现在,让我们在
foo中将
42更改为
21。pyx并重新加载文件:
>>> import importlib
>>> importlib.reload(foo)
21
>>> foo.doit()
42
>>> foo.__file__
'/home/XXX/.pyxbld/lib.linux-x86_64-3.6/foo.cpython-36m-x86_64-linux-gnu.so.reload2'
发生了什么事
pyximport构建了一个具有不同前缀的扩展(
reload2)并加载了它。这是成功的,因为新扩展名的名称/路径因新前缀而不同,我们可以看到加载时打印的内容。
但是,
foo。doit()
仍然是旧版本!如果我们查阅重新加载文档,我们会看到:
执行reload()时:
Python模块的代码被重新编译,模块级代码被重新执行,定义了一组新的对象,这些对象通过重用最初加载模块的加载器绑定到模块字典中的名称。扩展模块的
init
函数不会第二次调用。
init(即
PyInit_
要解决此问题,我们必须再次导入模块foo:
>>> import foo
>>> foo.doit()
21
现在,foo被尽可能地重新加载,这意味着可能仍有旧对象在使用。但我相信你知道你在做什么。
B:更改每个版本的扩展名称
foo\u prefix2。so等,并将其加载为另一种策略是构建模块foo。pyx作为前缀1。所以
然后
>>> import foo_perfixX as foo
这是IPython中的magic使用的策略,它使用cython代码的sha1哈希作为前缀。
可以使用imp.load_dynamic
(或在import lib
的帮助下实现IPython的方法,因为imp
已弃用):
from importlib._bootstrap _load
def load_dynamic(name, path, file=None):
"""
Load an extension module.
"""
import importlib.machinery
loader = importlib.machinery.ExtensionFileLoader(name, path)
# Issue #24748: Skip the sys.modules check in _load_module_shim;
# always load new extension
spec = importlib.machinery.ModuleSpec(
name=name, loader=loader, origin=path)
return _load(spec)
现在,将so文件放入不同的文件夹(或添加一些后缀),因此,dlopen将其视为与以前版本不同的文件,我们可以使用它:
# first argument (name="foo") tells how the init-function
# of the extension (i.e. `PyInit_<module_name>`) is called
foo = load_dynamic("foo", "1/foo.cpython-37m-x86_64-linux-gnu.so")
# now foo has new functionality:
foo = load_dynamic("foo", "2/foo.cpython-37m-x86_64-linux-gnu.so")
即使重新加载和重新加载扩展特别是有点hacky,出于原型设计的目的,我可能会使用pyximport
-解决方案...或者使用IPython和%%cython
-魔法。
我能够得到一个适用于Python 2的解决方案。x比Python 3简单得多。x、 无论出于何种原因,Cython似乎正在缓存它从中导入模块的可共享对象文件,即使在运行时重建和删除旧文件后,它仍然从旧的可共享对象文件导入。然而,无论如何这都不是必需的(当您导入foo.bar时,它不会创建一个),所以我们可以跳过它。
最大的问题是python保留了对旧模块的引用,即使在重新加载之后也是如此。普通python模块似乎可以找到,但与cython无关。为了解决这个问题,我运行execute两条语句来代替重载(foo.bar)
del sys.modules['foo.bar']
import foo.bar
这成功地(尽管可能效率较低)重新加载了cython模块。在运行该子进程的Python 3. x中,唯一仍然存在的问题会创建一个有问题的可共享对象。相反,请一起跳过这些,让importfoo.bar
与pyximPorter
模块一起发挥其魔力,并为您重新编译。我还在pyxinstall
命令中添加了一个选项,以指定与您在setup.py
中指定的语言级别相匹配
pyximport.install(reload_support=True, language_level=3)
总之:
runner.py
import sys
import pyximport
pyximport.install(reload_support=True, language_level=3)
import foo.bar
if __name__ == '__main__':
def reload_bar():
del sys.modules['foo.bar']
import foo.bar
foo.bar.say_hello()
input(" press enter to proceed ")
reload_bar()
foo.bar.say_hello()
其他两个文件保持不变
正在运行:
Hello!
press enter to proceed
-将foo/bar.pyx中的"Hello!"
替换为"Hello world!"
,然后按Enter。
Hello world!
问题 你想重新加载已经加载的模块,因为你对其源码进行了修改。 解决方案 使用imp.reload()来重新加载先前加载的模块。举个例子: >>> import spam >>> import imp >>> imp.reload(spam) <module 'spam' from './spam.py'> >>> 讨论 重新加载模块在开发和调试过程中常常很有用。但在生产环境中的代码使用会不安全,因
问题内容: 我有一台运行时间较长的Python服务器,并且希望能够在不重新启动服务器的情况下升级服务。最好的方法是什么? 问题答案: 你可以使用reload内置函数重新加载已导入的模块: 在Python 3中,已移至imp模块。在3.4中,不推荐使用importlib,而在中添加了。当定位到3或更高版本时,在调用reload或导入时参考相应的模块。 我认为这就是你想要的。诸如Django开发服务器
问题内容: 我有一个运行时间较长的Python服务器,并且希望能够在不重新启动服务器的情况下升级服务。最好的方法是什么? 问题答案: 您可以使用reload内置函数(仅适用于Python 3.4+)重新导入已导入的模块: 在Python 3中,已移至模块。在3.4中,imp不推荐使用,而reload在中添加了。当定位到3或更高版本时,在调用reload或导入时参考相应的模块。 我认为这就是您想要的
问题内容: 快速的问题,我已经尝试自己弄清楚这一点,但是在试图弄清页面为什么或如何重新加载以及正在/不应该执行其应做的工作时,使用会话变量可能会造成混淆。 在任何(非脚本)情况下,页面重新加载(使用JavaScript,f5,ctrl + f5,浏览器重新加载按钮等)是否会导致表单重新发布? (这与在C#代码中使用IfPost分支有关,例如下面的示例代码): 我只是需要知道在这里期望什么,以便可以
问题内容: 当用户访问未经授权的个人页面(例如个人资料)时,我的后端302重定向到控制器操作,该操作代替部分个人资料来提供登录部分。由于它302重定向到返回部分操作的动作,因此URL地址栏与用户尝试访问的页面(“ / profile”)没有变化。 我本来打算“修复”该问题,但实际上我认为它可以提供良好的用户体验,而不是将返回网址作为查询参数来处理。 想法是一旦他们登录,我只想重新加载当前路由,也可
问题内容: 我正在建立一个新的由AJAX驱动的网站,其中包含不同的部分。每个部分都需要一组新的Javascript函数才能运行。我宁愿不要一开始就加载每个脚本,因为可能会有很多脚本。 有没有一种方法可以使用AJAX加载新脚本并删除旧脚本(以确保类似的变量名或函数签名不存在兼容性问题)。 谢谢 编辑 -jQuery很好,它不必是老式的Javascript 问题答案: 三件事: 1)是,您可以加载新脚