From 0beffc76b4c9c3e36aa709d24a97d68e8a796f06 Mon Sep 17 00:00:00 2001 From: rmorotti Date: Mon, 24 Jun 2024 17:45:02 +0100 Subject: [PATCH] PERF: optimize display of installed packages at the end of pip install get_distribution("package_name") does a loop over all packages to find the intended package name. the code to display installed packages was accidentally O(N^2) for iterating over packages and calling get_distribution() on each. Resolves: #12791 --- news/12791.bugfix.rst | 2 ++ src/pip/_internal/commands/install.py | 18 ++++++++++-------- src/pip/_internal/metadata/importlib/_envs.py | 3 ++- 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 news/12791.bugfix.rst diff --git a/news/12791.bugfix.rst b/news/12791.bugfix.rst new file mode 100644 index 00000000000..7575c5b84fa --- /dev/null +++ b/news/12791.bugfix.rst @@ -0,0 +1,2 @@ +Improve performance. Fix code to display installed packages at the end of + pip install, was O(n^2) to enumerate packages due to accidental loop in loop. diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index d5b06c8c785..2185703b6c6 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -7,6 +7,7 @@ from optparse import SUPPRESS_HELP, Values from typing import List, Optional +from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.rich import print_json from pip._internal.cache import WheelCache @@ -472,16 +473,17 @@ def run(self, options: Values, args: List[str]) -> int: ) env = get_environment(lib_locations) + # Display a summary of installed packages, with extra care to + # display a package name as it was requested by the user. installed.sort(key=operator.attrgetter("name")) items = [] - for result in installed: - item = result.name - try: - installed_dist = env.get_distribution(item) - if installed_dist is not None: - item = f"{item}-{installed_dist.version}" - except Exception: - pass + installed_versions = {} + for distribution in env.iter_all_distributions(): + installed_versions[distribution.canonical_name] = distribution.version + for package in installed: + display_name = package.name + version = installed_versions.get(canonicalize_name(display_name), None) + item = f"{display_name}-{version}" items.append(item) if conflicts is not None: diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 2df738fc738..c82e1ffc951 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -181,9 +181,10 @@ def _iter_distributions(self) -> Iterator[BaseDistribution]: yield from finder.find_linked(location) def get_distribution(self, name: str) -> Optional[BaseDistribution]: + canonical_name = canonicalize_name(name) matches = ( distribution for distribution in self.iter_all_distributions() - if distribution.canonical_name == canonicalize_name(name) + if distribution.canonical_name == canonical_name ) return next(matches, None)