检测python中的循环依赖项

时间:2011-08-07 18:10:38

标签: python circular-dependency google-closure-library

我不明白为什么_ResolveDependencies类中的DepsTree方法可以检测循环依赖。如果a需要beb需要e,它似乎可以检测到这种情况,但它不是循环依赖项。

class DepsTree(object):
  """Represents the set of dependencies between source files."""

  def __init__(self, sources):
    """Initializes the tree with a set of sources.

    Args:
      sources: A set of JavaScript sources.

    Raises:
      MultipleProvideError: A namespace is provided by muplitple sources.
      NamespaceNotFoundError: A namespace is required but never provided.
    """

    self._sources = sources
    self._provides_map = dict()

    # Ensure nothing was provided twice.
    for source in sources:
      for provide in source.provides:
        if provide in self._provides_map:
          raise MultipleProvideError(
              provide, [self._provides_map[provide], source])

        self._provides_map[provide] = source

    # Check that all required namespaces are provided.
    for source in sources:
      for require in source.requires:
        if require not in self._provides_map:
          raise NamespaceNotFoundError(require, source)

  def GetDependencies(self, required_namespaces):
    """Get source dependencies, in order, for the given namespaces.

    Args:
      required_namespaces: A string (for one) or list (for one or more) of
        namespaces.

    Returns:
      A list of source objects that provide those namespaces and all
      requirements, in dependency order.

    Raises:
      NamespaceNotFoundError: A namespace is requested but doesn't exist.
      CircularDependencyError: A cycle is detected in the dependency tree.
    """
    if type(required_namespaces) is str:
      required_namespaces = [required_namespaces]

    deps_sources = []

    for namespace in required_namespaces:
      for source in DepsTree._ResolveDependencies(
          namespace, [], self._provides_map, []):
        if source not in deps_sources:
          deps_sources.append(source)

    return deps_sources

  @staticmethod
  def _ResolveDependencies(required_namespace, deps_list, provides_map,
                           traversal_path):
    """Resolve dependencies for Closure source files.

    Follows the dependency tree down and builds a list of sources in dependency
    order.  This function will recursively call itself to fill all dependencies
    below the requested namespaces, and then append its sources at the end of
    the list.

    Args:
      required_namespace: String of required namespace.
      deps_list: List of sources in dependency order.  This function will append
        the required source once all of its dependencies are satisfied.
      provides_map: Map from namespace to source that provides it.
      traversal_path: List of namespaces of our path from the root down the
        dependency/recursion tree.  Used to identify cyclical dependencies.
        This is a list used as a stack -- when the function is entered, the
        current namespace is pushed and popped right before returning.
        Each recursive call will check that the current namespace does not
        appear in the list, throwing a CircularDependencyError if it does.

    Returns:
      The given deps_list object filled with sources in dependency order.

    Raises:
      NamespaceNotFoundError: A namespace is requested but doesn't exist.
      CircularDependencyError: A cycle is detected in the dependency tree.
    """

    source = provides_map.get(required_namespace)
    if not source:
      raise NamespaceNotFoundError(required_namespace)

    if required_namespace in traversal_path:
      traversal_path.append(required_namespace)  # do this *after* the test

      # This must be a cycle.
      raise CircularDependencyError(traversal_path)

    traversal_path.append(required_namespace)

    for require in source.requires:

      # Append all other dependencies before we append our own.
      DepsTree._ResolveDependencies(require, deps_list, provides_map,
                                    traversal_path)
    deps_list.append(source)

    traversal_path.pop()

    return deps_list

1 个答案:

答案 0 :(得分:2)

简短版本:_ResolveDependencies执行依赖关系树的深度优先遍历,记录路径。如果遇到路径中已有的节点,则表示存在循环。

_ResolveDependencies遍历Source.requires所体现的依赖关系林。在任何时间点对_ResolveDependencies的活动调用对应于通过依赖关系树的路径(因此_path中的traversal_path);这是通过在递归之前向traversal_path添加命名空间并在之后删除它来跟踪的。换句话说,当且仅当traversal_path的调用正在处理命名空间时,命名空间才在_ResolveDependencies中。如果要求_ResolveDependencies检查traversal_path中已存在的命名空间,则对_ResolveDependencies的不同调用正在处理命名空间,并且存在从节点到其自身的路径,因此周期。

作为一个例子,考虑最简单的依赖循环:“a”要求“c”需要“a”。让我们也投入一个“a”需要“b”来表示当分支中没有依赖时会发生什么。您将获得以下调用序列(在伪python中)。大多数情况下,变量的值替换变量名称(例如a的{​​{1}})。注意:这不代表源代码,而是代表程序运行时执行的语句序列。

_RD(ns={name:a, req: [b, c]}, path=[]):
  if a in path: # false
  path += [a]
  for each subns in [b,c]:
    _RD(ns={name: b, req: []}, path=[a]):
      if b in path: # false
      path += [b]
      for each subns in []:
      # done with this branch
      path -= [b]      
    _RD(ns={name: c, req: [a]}, path=[a]):
      if c in path: # false
      path += [c]
      for each subns in [a]:
        _RD(ns={name: a req: [b,c]}, path=[a, c]):
          if a in path: # true
            throw CircularDependencyError