Python中的递归reload()和"from some_module import *"的问题

4

我有一个叫做KosmoSuite的模块,在以下的__init__.py中被初始化:

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

在文件chemical_fuels.py,orbital_mechanics.py,thermodynamics.py,rail_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)。有没有替代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 解释器也一样。在这个问题中,提到了IPython中的深度重新加载(dreload)。我不确定它是否完全满足此功能,但我也不喜欢IPython。

3个回答

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. 如果您使用旧版模块创建了任何对象,则它们将保留其旧类。

尽管如此,在交互式会话中开发和测试模块时,我曾经使用过它,并且如果您考虑到这些限制,它大部分情况下都可以正常工作。


听起来不错,但我遇到了错误:bak_modules = sys.modules[:] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type ...也许是因为我正在使用Python 2.7或Spyder? - Prokop Hapala
抱歉,我是凭记忆说的。你需要使用 sys.modules.copy() 并明确删除键才能使其正常工作。我已经更新了答案。 - otus

2
重新加载Python模块并不像你想象的那样简单。一旦你创建了一个对象ks,它就引用了类和代码。如果重新加载一个模块,它将定义一个与原始名称相同的新类。对象仍然引用原始类而不是新类。
你可能能够更改现有对象的类,但如果它们引用其他对象,则必须尝试更改那些类等等。你会与类和模块系统作斗争,试图自己重新实现重载的重要部分。
你最好找到适合Python模块行为的工作流程。IPython笔记本让你可以交互式地进行实验,但捕获代码以便从头重新运行。可能还有其他解决方案。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接