检测python中的循环依赖

发布于 2024-11-28 05:46:32 字数 4190 浏览 0 评论 0原文

我不明白为什么 DepsTree 类中的 _ResolveDependencies 方法可以检测循环依赖项。似乎它可以检测到 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

I don't understand why the _ResolveDependencies method in DepsTree class can detect circular dependencies. Seems it can detect the situation if a requires b and e and b requires e, but it's not a circular dependencies.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

扮仙女 2024-12-05 05:46:32

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

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

作为一个例子,考虑最简单的依赖循环:“a”需要“c”需要“a”。我们还添加一个“a”需要“b”来显示当分支中没有依赖项时会发生什么。您将得到以下调用序列(以伪 python 形式)。大多数情况下,变量的值会替换变量名称(例如,a 替换ns.name)。注意:这并不代表源代码,而是代表程序运行时执行的语句序列。

_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

Short version: _ResolveDependencies performs a depth-first traversal of the dependency tree, recording the path. If it encounters a node that is already in the path, this means there's a cycle.

_ResolveDependencies traverses the dependency forest embodied by Source.requires. The active calls to _ResolveDependencies at any point in time corresponds to a path through the dependency tree (hence the _path in traversal_path); this is tracked by adding a namespace to traversal_path before recursion and removing it after. In other words, a namespace is in traversal_path if and only if an invocation of _ResolveDependencies is processing the namespace. If _ResolveDependencies is asked to check a namespace that already exists in traversal_path, then a different call to _ResolveDependencies is processing the namespace, and there exists a path from a node to itself, hence a cycle.

As an example, consider the simplest dependency cycle: "a" requires "c" requires "a". Let's also throw in an "a" requires "b" to show what happens when there isn't a dependency in a branch. You'd get the following call sequence (in pseudo-python). Most of the time the value of a variable is substituted for the variable name (e.g. a for ns.name). Note: this doesn't represent source code but rather the sequence of statements that are executed when the program runs.

_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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文