如何在Python中查找依赖于特定模块的模块列表

9
为了缩短 Python 网页应用程序的开发时间,我尝试在最近修改过的模块中使用 reload()。通过专门的网页(Web 应用程序的开发版本的一部分),可以列出最近修改过的模块列表(py 文件的修改时间戳晚于相应的 pyc 文件)。完整的模块列表是从 sys.modules 中获取的(我会筛选该列表,仅关注属于我的包的模块)。
在某些情况下,重新加载单个 Python 文件似乎有效,而在其他情况下则无效。我猜想,所有依赖于已修改模块的模块都应该重新加载,并且重新加载应该按正确顺序进行。
我正在寻找一种方法来获取特定模块导入的模块列表。在 Python 中是否有这种内省的方法呢?
我知道我的方法可能不是 100% 可靠的,最安全的方式是重新加载所有内容,但如果快速的方法对大多数情况都有效,那么对于开发目的来说就足够了。
针对 DJango 自动重新加载的评论的回复:
@Glenn Maynard,谢谢,我已经了解了 DJango 的自动重新加载。我的 Web 应用程序基于 Zope 3,并且由于有大量的包和许多基于 ZCML 的初始化,总重启需要花费约 10 到 30 秒,如果数据库大小更大,则需要花费更多时间。我试图缩短重启期间花费的时间。当我觉得自己已经做了很多更改时,我通常会选择完全重新启动,但更频繁地,我只是在这里和那里修改了几行代码,我不想花费太多时间。开发设置与生产设置完全独立,通常,如果 reload 出现问题,由于应用程序页面开始显示不合逻辑的信息或抛出异常,问题将变得明显。我非常有兴趣探索选择性重新加载是否可行。

最好像Django的自动重新加载器一样做,当源文件被修改时,它会完全重新执行后端。我不知道有什么缺点;你修改一个文件,一两秒钟后就会自动重新加载所有内容。只在“大多数”情况下有效的东西对于开发来说非常糟糕;当它无法正常工作时,你只是在请求未来遭受痛苦的打击。 - Glenn Maynard
因为有一个重复的问题,所以我重新回到这里,并补充说明“现在(2013年)减少使用zope时重新加载时间的方法是使用sauna.reload”。 - jsbueno
3个回答

5
因此,这回答了“查找依赖于给定模块的模块列表”的问题,而不是最初提出的问题,我已经回答过了。
事实证明,这有点更加复杂:必须找到所有已加载模块的依赖树,并为每个模块反转它,同时保留不会破坏事物的加载顺序。
我还在巴西的Python维基上发布了这篇文章: http://www.python.org.br/wiki/RecarregarModulos
#! /usr/bin/env python
# coding: utf-8

# Author: João S. O. Bueno
# Copyright (c) 2009 - Fundação CPqD
# License: LGPL V3.0


from types import ModuleType, FunctionType, ClassType
import sys

def find_dependent_modules():
    """gets a one level inversed module dependence tree"""
    tree = {}
    for module in sys.modules.values():
        if module is None:
            continue
        tree[module] = set()
        for attr_name in dir(module):
            attr = getattr(module, attr_name)
            if isinstance(attr, ModuleType):
                tree[module].add(attr)
            elif type(attr) in (FunctionType, ClassType):        
                tree[module].add(attr.__module__)
    return tree


def get_reversed_first_level_tree(tree):
    """Creates a one level deep straight dependence tree"""
    new_tree = {}
    for module, dependencies in tree.items():
        for dep_module in dependencies:
            if dep_module is module:
                continue
            if not dep_module in new_tree:
                new_tree[dep_module] = set([module])
            else:
                new_tree[dep_module].add(module)
    return new_tree

def find_dependants_recurse(key, rev_tree, previous=None):
    """Given a one-level dependance tree dictionary,
       recursively builds a non-repeating list of all dependant
       modules
    """
    if previous is None:
        previous = set()
    if not key in rev_tree:
        return []
    this_level_dependants = set(rev_tree[key])
    next_level_dependants = set()
    for dependant in this_level_dependants:
        if dependant in previous:
            continue
        tmp_previous = previous.copy()
        tmp_previous.add(dependant)
        next_level_dependants.update(
             find_dependants_recurse(dependant, rev_tree,
                                     previous=tmp_previous,
                                    ))
    # ensures reloading order on the final list
    # by postponing the reload of modules in this level
    # that also appear later on the tree
    dependants = (list(this_level_dependants.difference(
                        next_level_dependants)) +
                  list(next_level_dependants))
    return dependants

def get_reversed_tree():
    """
        Yields a dictionary mapping all loaded modules to
        lists of the tree of modules that depend on it, in an order
        that can be used fore reloading
    """
    tree = find_dependent_modules()
    rev_tree = get_reversed_first_level_tree(tree)
    compl_tree = {}
    for module, dependant_modules in rev_tree.items():
        compl_tree[module] = find_dependants_recurse(module, rev_tree)
    return compl_tree

def reload_dependences(module):
    """
        reloads given module and all modules that
        depend on it, directly and otherwise.
    """
    tree = get_reversed_tree()
    reload(module)
    for dependant in tree[module]:
        reload(dependant)

这在我进行的所有测试中都很好地工作 - 但我不建议滥用它。 但是,如果需要在编辑几行代码后更新正在运行的zope2服务器,我认为我会使用它。


嗨,我发现仅仅在dir(module)中查找ModuleType属性是不够的。很多时候,导入看起来像“from xyz import abc”。为了处理这种情况,我们还应该考虑dir(module)列表中的FunctionType和ClassType属性,并且对于那些属性,我们应该获取它们相应的getattr(attr, 'module')并将它们添加到依赖项中。 - Shailesh Kumar
确实。我得修复这个问题 - 或者从两个地方都删除代码 - 现在它太复杂了,必须对所有需要的人都起作用。 - jsbueno
ClassType在3.4版本中(可能更早)不再是一种类型,可以安全地假设MethodType将成为它的替代品? - Brian S

3
一些自省可以解决问题:
from types import ModuleType

def find_modules(module, all_mods = None):
   if all_mods is None:
      all_mods = set([module])
   for item_name in dir(module):
       item = getattr(module, item_name)
       if isinstance(item, ModuleType) and not item in all_mods:
           all_mods.add(item)
           find_modules(item, all_mods)
   return all_mods

这会给你一个包含所有已加载模块的集合 - 只需使用第一个模块作为唯一参数调用函数。然后,您可以迭代重载结果集,如下所示: [reload (m) for m in find_modules(<module>)]


只是想理解这段代码。
  • 从给定的模块x开始
  • 创建一个已被x导入的空模块集合
  • 迭代dir(x)以识别所有作为模块的项
  • 将它们添加到x的模块集合中
  • 递归执行此操作,以查找x的所有依赖项
我可能需要从这里开始,并进行反向映射,以识别所有依赖于特定模块的模块集合。
- Shailesh Kumar
等一下——你是需要一个“Python 模块依赖列表”,还是需要一个“所有依赖于特定模块的模块列表”? 我可以提供后者的代码,不会比这个复杂多少——但问题的措辞是针对前者的。 - jsbueno
@jsbueno 是的,你说得对,我错误地表达了主题行 :( 我现在已经修改了主题行。我正在寻找所有依赖于特定模块的模块。 - Shailesh Kumar

2
您可能想看一下Ian Bicking的Paste重新加载模块,它已经实现了您想要的功能:http://pythonpaste.org/modules/reloader?highlight=reloader。它不能给您一个特定的依赖文件列表(只有在打包人员勤奋并正确指定依赖关系时才是技术上可行的),但查看代码将为您提供准确的修改文件列表以重新启动进程。

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