Skip to content

Commit 19167fe

Browse files
authored
PERF, validation: wrap inspect.getsourcelines with cache (#532)
* wrap inspect.getsourcelines with cache
1 parent 1e60071 commit 19167fe

File tree

1 file changed

+15
-9
lines changed

1 file changed

+15
-9
lines changed

numpydoc/validate.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
from copy import deepcopy
10-
from typing import Dict, List, Set, Optional
10+
from typing import Dict, List, Set, Optional, Any
1111
import ast
1212
import collections
1313
import functools
@@ -116,7 +116,7 @@
116116
# We have to balance memory usage with performance here. It shouldn't be too
117117
# bad to store these `dict`s (they should be rare), but to be safe let's keep
118118
# the limit low-ish. This was set by looking at scipy, numpy, matplotlib,
119-
# and pandas and they had between ~500 and ~1300 .py files as of 2023-08-16.
119+
# and pandas, and they had between ~500 and ~1300 .py files as of 2023-08-16.
120120
@functools.lru_cache(maxsize=2000)
121121
def extract_ignore_validation_comments(
122122
filepath: Optional[os.PathLike],
@@ -212,7 +212,7 @@ def error(code, **kwargs):
212212
message : str
213213
Error message with variables replaced.
214214
"""
215-
return (code, ERROR_MSGS[code].format(**kwargs))
215+
return code, ERROR_MSGS[code].format(**kwargs)
216216

217217

218218
class Validator:
@@ -290,24 +290,30 @@ def source_file_name(self):
290290

291291
except TypeError:
292292
# In some cases the object is something complex like a cython
293-
# object that can't be easily introspected. An it's better to
293+
# object that can't be easily introspected. And it's better to
294294
# return the source code file of the object as None, than crash
295295
pass
296296
else:
297297
return fname
298298

299+
# When calling validate, files are parsed twice
300+
@staticmethod
301+
@functools.lru_cache(maxsize=4000)
302+
def _getsourcelines(obj: Any):
303+
return inspect.getsourcelines(obj)
304+
299305
@property
300306
def source_file_def_line(self):
301307
"""
302308
Number of line where the object is defined in its file.
303309
"""
304310
try:
305311
if isinstance(self.code_obj, property):
306-
sourcelines = inspect.getsourcelines(self.code_obj.fget)
312+
sourcelines = self._getsourcelines(self.code_obj.fget)
307313
elif isinstance(self.code_obj, functools.cached_property):
308-
sourcelines = inspect.getsourcelines(self.code_obj.func)
314+
sourcelines = self._getsourcelines(self.code_obj.func)
309315
else:
310-
sourcelines = inspect.getsourcelines(self.code_obj)
316+
sourcelines = self._getsourcelines(self.code_obj)
311317
# getsourcelines will return the line of the first decorator found for the
312318
# current function. We have to find the def declaration after that.
313319
def_line = next(
@@ -320,7 +326,7 @@ def source_file_def_line(self):
320326
return sourcelines[-1] + def_line
321327
except (OSError, TypeError):
322328
# In some cases the object is something complex like a cython
323-
# object that can't be easily introspected. An it's better to
329+
# object that can't be easily introspected. And it's better to
324330
# return the line number as None, than crash
325331
pass
326332

@@ -613,7 +619,7 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
613619
else:
614620
doc = validator_cls(obj_name=obj_name, **validator_kwargs)
615621

616-
# lineno is only 0 if we have a module docstring in the file and we are
622+
# lineno is only 0 if we have a module docstring in the file, and we are
617623
# validating that, so we change to 1 for readability of the output
618624
ignore_validation_comments = extract_ignore_validation_comments(
619625
doc.source_file_name

0 commit comments

Comments
 (0)