Skip to content

Commit 411b981

Browse files
authoredApr 30, 2024··
Merge pull request #12657 from notatallshaw/eagerly-calculate-strings-and-hashes-for-requirements-and-candidate
2 parents f18bebd + 1e510e3 commit 411b981

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed
 

‎news/12657.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Further improve resolution performance of large dependency trees, by caching hash calculations.

‎src/pip/_internal/resolution/resolvelib/candidates.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def __init__(
154154
self._name = name
155155
self._version = version
156156
self.dist = self._prepare()
157+
self._hash: Optional[int] = None
157158

158159
def __str__(self) -> str:
159160
return f"{self.name} {self.version}"
@@ -162,7 +163,11 @@ def __repr__(self) -> str:
162163
return f"{self.__class__.__name__}({str(self._link)!r})"
163164

164165
def __hash__(self) -> int:
165-
return hash((self.__class__, self._link))
166+
if self._hash is not None:
167+
return self._hash
168+
169+
self._hash = hash((self.__class__, self._link))
170+
return self._hash
166171

167172
def __eq__(self, other: Any) -> bool:
168173
if isinstance(other, self.__class__):

‎src/pip/_internal/resolution/resolvelib/requirements.py

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any
1+
from typing import Any, Optional
22

33
from pip._vendor.packaging.specifiers import SpecifierSet
44
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
@@ -51,8 +51,18 @@ class SpecifierRequirement(Requirement):
5151
def __init__(self, ireq: InstallRequirement) -> None:
5252
assert ireq.link is None, "This is a link, not a specifier"
5353
self._ireq = ireq
54+
self._equal_cache: Optional[str] = None
55+
self._hash: Optional[int] = None
5456
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
5557

58+
@property
59+
def _equal(self) -> str:
60+
if self._equal_cache is not None:
61+
return self._equal_cache
62+
63+
self._equal_cache = str(self._ireq)
64+
return self._equal_cache
65+
5666
def __str__(self) -> str:
5767
return str(self._ireq.req)
5868

@@ -62,10 +72,14 @@ def __repr__(self) -> str:
6272
def __eq__(self, other: object) -> bool:
6373
if not isinstance(other, SpecifierRequirement):
6474
return NotImplemented
65-
return str(self._ireq) == str(other._ireq)
75+
return self._equal == other._equal
6676

6777
def __hash__(self) -> int:
68-
return hash(str(self._ireq))
78+
if self._hash is not None:
79+
return self._hash
80+
81+
self._hash = hash(self._equal)
82+
return self._hash
6983

7084
@property
7185
def project_name(self) -> NormalizedName:
@@ -114,15 +128,29 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
114128
def __init__(self, ireq: InstallRequirement) -> None:
115129
assert ireq.link is None, "This is a link, not a specifier"
116130
self._ireq = install_req_drop_extras(ireq)
131+
self._equal_cache: Optional[str] = None
132+
self._hash: Optional[int] = None
117133
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
118134

135+
@property
136+
def _equal(self) -> str:
137+
if self._equal_cache is not None:
138+
return self._equal_cache
139+
140+
self._equal_cache = str(self._ireq)
141+
return self._equal_cache
142+
119143
def __eq__(self, other: object) -> bool:
120144
if not isinstance(other, SpecifierWithoutExtrasRequirement):
121145
return NotImplemented
122-
return str(self._ireq) == str(other._ireq)
146+
return self._equal == other._equal
123147

124148
def __hash__(self) -> int:
125-
return hash(str(self._ireq))
149+
if self._hash is not None:
150+
return self._hash
151+
152+
self._hash = hash(self._equal)
153+
return self._hash
126154

127155

128156
class RequiresPythonRequirement(Requirement):
@@ -131,6 +159,7 @@ class RequiresPythonRequirement(Requirement):
131159
def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
132160
self.specifier = specifier
133161
self._specifier_string = str(specifier) # for faster __eq__
162+
self._hash: Optional[int] = None
134163
self._candidate = match
135164

136165
def __str__(self) -> str:
@@ -140,7 +169,11 @@ def __repr__(self) -> str:
140169
return f"{self.__class__.__name__}({str(self.specifier)!r})"
141170

142171
def __hash__(self) -> int:
143-
return hash((self._specifier_string, self._candidate))
172+
if self._hash is not None:
173+
return self._hash
174+
175+
self._hash = hash((self._specifier_string, self._candidate))
176+
return self._hash
144177

145178
def __eq__(self, other: Any) -> bool:
146179
if not isinstance(other, RequiresPythonRequirement):

0 commit comments

Comments
 (0)
Please sign in to comment.