Repository: pypa/pip
File: src/pip/_internal/resolution/resolvelib/resolver.py (Python)

Code before:

    This implementation may change at any point in the future without prior
    notice.

    We take the length for the longest path to any node from root, ignoring any
    paths that contain a single node twice (i.e. cycles). This is done through
    a depth-first search through the graph, while keeping track of the path to
    the node.

    Cycles in the graph result would result in node being revisited while also
    being it's own path. In this case, take no action. This helps ensure we
    don't get stuck in a cycle.

    When assigning weight, the longer path (i.e. larger length) is preferred.
    """
    path: Set[Optional[str]] = set()
    weights: Dict[Optional[str], int] = {}

    # Make a copy of the graph and edit the copy,
    # instead of changing the original.
    cgraph: "DirectedGraph[Optional[str]]" = graph.copy()

    def simplify_graph() -> None:
        leaves = set()
        for key in cgraph:
            if not list(cgraph.iter_children(key)) and key is not None:
                leaves.add(key)
        if not leaves:
            # We are done simplifying.
            return
        # Calculate the weight for the leaves.
        weight = len(cgraph) - 1
        for leave in leaves:
            weights[leave] = weight
        # Remove the leaves from the copy of the graph, making the copy simpler.
        for leave in leaves:
            cgraph.remove(leave)
        # Now that we have a simpler graph, try to simplify it again.
        simplify_graph()

    def visit(node: Optional[str]) -> None:
        if node in path:
            # We hit a cycle, so we'll break it here.
            return

        # Time to visit the children!
        path.add(node)
        for child in cgraph.iter_children(node):
            visit(child)
        path.remove(node)


Review comment (performance):
Given that `key is not None` is very close to free and constructing a list of iter_children might be expensive how about reversing this conditional so the inexpensive check comes first?

Second, 
```python
not list(cgraph.iter_children(key))
```
seems like a very expensive way of testing the empty-ness of an iterable (construct a list of possibly many items to ask if it's non-empty).  Maybe something like this:

```python
for itm in possibly_empty_iter:
    pass # non-empty case
    break
else:
    pass # empty case
```


Code after:
    """Assign weights to each node based on how "deep" they are.

    This implementation may change at any point in the future without prior
    notice.

    We first simplify the dependency graph by pruning any leaves and giving them
    the highest weight: a package without any dependencies should be installed
    first. This is done again and again in the same way, giving ever less weight
    to the newly found leaves. The loop stops when no leaves are left: all
    remaining packages have at least one dependency left in the graph.

    Then we continue with the remaining graph, by taking the length for the
    longest path to any node from root, ignoring any paths that contain a single
    node twice (i.e. cycles). This is done through a depth-first search through
    the graph, while keeping track of the path to the node.

    Cycles in the graph result would result in node being revisited while also
    being on its own path. In this case, take no action. This helps ensure we
    don't get stuck in a cycle.

    When assigning weight, the longer path (i.e. larger length) is preferred.
    """
    path: Set[Optional[str]] = set()
    weights: Dict[Optional[str], int] = {}

    def visit(node: Optional[str]) -> None:
        if node in path:
            # We hit a cycle, so we'll break it here.
            return

        # Time to visit the children!
        path.add(node)
        for child in graph.iter_children(node):
            visit(child)
        path.remove(node)

        last_known_parent_count = weights.get(node, 0)
        weights[node] = max(last_known_parent_count, len(path))

    # Simplify the graph, pruning leaves that have no dependencies.
    # This is needed for large graphs (say over 200 packages) because the
    # `visit` function is exponentially slower then, taking minutes.
    # See https://github.com/pypa/pip/issues/10557
    # We will loop until we explicitly break the loop.
    while True:
        leaves = set()
        for key in graph:
            if key is None:
                continue
            for _child in graph.iter_children(key):
                # This means we have at least one child