如何找到依赖于特定模块的Python模块列表

9 投票
3 回答
3274 浏览
提问于 2025-04-15 16:34

为了减少我基于Python的网页应用开发时间,我正在尝试使用reload()来重新加载我最近修改过的模块。这个reload()是通过一个专门的网页来实现的(这是开发版本的一部分),这个网页列出了最近修改过的模块(并且这些模块的.py文件的修改时间晚于对应的.pyc文件)。我从sys.modules获取完整的模块列表,然后过滤出我包中相关的模块。

重新加载单个Python文件在某些情况下似乎有效,但在其他情况下却不行。我猜想,所有依赖于被修改模块的模块都应该被重新加载,并且重新加载的顺序也很重要。

我想找到一种方法,获取某个特定模块所导入的模块列表。有没有办法在Python中进行这种自省?

我明白我的方法可能不是100%可靠,最安全的方式是重新加载所有模块,但如果快速的方法在大多数情况下都能奏效,那对于开发来说就足够了。

关于Django自动重载的评论回复

@Glenn Maynard,谢谢,我之前读过关于Django自动重载的内容。我的网页应用是基于Zope 3的,由于有很多包和大量基于ZCML的初始化,完全重启大约需要10到30秒,甚至更多,尤其是当数据库比较大的时候。我正在尝试减少重启所花费的时间。当我觉得自己做了很多修改时,通常会选择完全重启,但更多时候我只是改了几行代码,这样我就不想花那么多时间。开发环境和生产环境是完全独立的,通常如果reload出错,问题会很明显,因为应用页面会显示不合逻辑的信息或者抛出异常。我非常想探索选择性重载是否可行。

3 个回答

2

你可以看看Ian Bicking的Paste重载模块,它已经实现了你想要的功能:

http://pythonpaste.org/modules/reloader?highlight=reloader

这个模块并不会给你一个具体的依赖文件列表(这只有在打包者认真并正确指定依赖关系的情况下才有可能),但是查看代码可以让你准确知道哪些文件被修改了,从而可以重新启动程序。

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>)]

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服务器,我觉得我会自己用这个方法。

撰写回答