当前位置: 首页 > 知识库问答 >
问题:

是否使用pyximport重新加载模块?

于捷
2023-03-14

我有一个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。

共有2个答案

徐飞尘
2023-03-14

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。pyx作为前缀1。所以然后foo\u prefix2。so等,并将其加载为

>>> 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-魔法。

秦安宁
2023-03-14

我能够得到一个适用于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.barpyximPorter模块一起发挥其魔力,并为您重新编译。我还在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)是,您可以加载新脚