在Python中递归重载() "from some_module import *

4 投票
3 回答
1736 浏览
提问于 2025-04-18 08:08

我有一个叫做 KosmoSuite 的模块,它是通过 __init__.py 文件初始化的。

...
from chemical_fuels import *
from orbital_mechanics import *
from termodynamics import *
from rail_guns import *
...

在文件 chemical_fuels.pyorbital_mechanics.pytermodynamics.pyrail_guns.py 中,有一些数据表、常量和函数,用来进行一些物理计算(比如函数 orbital_mechanics.escape_velocity(M,R) 可以计算给定质量和半径的行星的逃逸速度)。

我想把 Python 解释器当作一个互动式的空间问题计算器。

但是,问题在于互动开发和调试。当我这样做时:

>>> import KosmoSuite as ks
# ... modify something in orbital_mechanics.escape_velocity( ) ...
>>> reload(ks)
>>> ks.orbital_velocity( R, M)

但是 ks.orbital_velocity(R, M) 并没有受到我对 orbital_mechanics.escape_velocity() 修改的影响。有没有什么替代方法可以做到 reload(ks) 的效果(也就是递归地重新加载通过 from some_module import * 导入的所有对象、常量和函数)?

更好的是,能有类似这样的功能:

>>> from KosmoSuite import *
# ... modify something in orbital_mechanics.escape_velocity( ) ...
>>> from KosmoSuite reimport *
>>> orbital_velocity( R, M)

附注:我现在使用的是 Spyder(Python(x,y)),但在默认的 Python 解释器中也是一样的。在 这个问题 中提到了一些关于 深度重载(dreload)在 IPython 中 的内容。我不确定它是否能完全做到这一点,但我反正不喜欢 IPython。

3 个回答

0

是的,只要Python支持元编程,这个事情就可以做到。

下面是我写的一个函数,用来完成这个任务(Python3):

import importlib, sys

def reload_all(top_module, max_depth=20):
    '''
    A reload function, which recursively traverses through
    all submodules of top_module and reloads them from most-
    nested to least-nested. Only modules containing __file__
    attribute could be reloaded.

    Returns a dict of not reloaded(due to errors) modules:
      key = module, value = exception
    Optional attribute max_depth defines maximum recursion
    limit to avoid infinite loops while tracing
    '''
    module_type = type(importlib)   # get the 'module' type
    for_reload = dict() # modules to reload: K=module, V=depth

    def trace_reload(module, depth):    # recursive
        nonlocal for_reload
        depth += 1
        if type(module) == module_type and depth < max_depth:
            # if module is deeper and could be reloaded
            if (for_reload.get(module, 0) < depth
                and hasattr(module, '__file__') ):
                    for_reload[module] = depth
            # trace through all attributes recursively       
            for name, attr in module.__dict__.items():
                trace_reload(attr, depth)


    trace_reload(top_module, 0)         # start tracing
    reload_list = sorted(for_reload, reverse=True,
                         key=lambda k:for_reload[k])
    not_reloaded = dict()
    for module in reload_list:
        try:
            importlib.reload(module)
        except:     # catch and write all errors
            not_reloaded[module]=sys.exc_info()[0]

    return not_reloaded

这个函数的说明已经很清楚了。如果你有改进的想法,可以看看这个GitHub项目:https://github.com/thodnev/reload_all

2

在Python中,重新加载模块的方式可能和你想的不太一样。一旦你创建了一个对象ks,这个对象就会和它所属的类有联系,也就是和代码有关系。如果你重新加载一个模块,Python会定义一个新的类,名字和原来的类一样。但是,这个对象仍然指向原来的类,而不是新的类。

你可能可以尝试改变已经存在的对象的类,但如果这些对象还指向其他对象,那你就得去改变那些对象的类,等等。这样做就像是在和类和模块的系统作斗争,自己重新实现重新加载的很多部分。

与其这样,不如找一个适合Python模块本身行为的工作流程。IPython notebook可以让你进行互动实验,同时把代码保存下来,以便从头再运行。可能还有其他解决方案。

3

一种比较笨重的解决办法是,在你导入模块链之前,先保存一下sys.modules的状态,然后在再次导入模块之前,把它恢复到原来的状态。

import sys
bak_modules = sys.modules.copy()
import KosmoSuite as ks
# do stuff with ks
# edit KosmoSuite
for k in sys.modules.keys():
    if not k in bak_modules:
        del sys.modules[k]
import KosmoSuite as ks

不过,这里有几个注意事项:

  1. 你可能需要重新导入一些在此期间已经导入的、不相关的模块。
  2. 如果你用旧版本的模块创建了任何对象,它们会保留旧版本的类。

尽管如此,我在开发一个模块并在交互式会话中测试时用过这个方法,如果考虑到这些限制,大部分情况下它还是能正常工作的。

撰写回答