Skip to content

Commit cd6abe2

Browse files
2trvljaracopicnixz
authored
gh-128641: Fix ConfigParser.read Perfomance Regression (#129596)
--------- Co-authored-by: Jason R. Coombs <[email protected]> Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 0718201 commit cd6abe2

File tree

2 files changed

+25
-29
lines changed

2 files changed

+25
-29
lines changed

Lib/configparser.py

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@
154154
import os
155155
import re
156156
import sys
157-
import types
158157

159158
__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
160159
"NoOptionError", "InterpolationError", "InterpolationDepthError",
@@ -570,35 +569,36 @@ def __init__(self):
570569

571570

572571
class _Line(str):
572+
__slots__ = 'clean', 'has_comments'
573573

574574
def __new__(cls, val, *args, **kwargs):
575575
return super().__new__(cls, val)
576576

577-
def __init__(self, val, prefixes):
578-
self.prefixes = prefixes
577+
def __init__(self, val, comments):
578+
trimmed = val.strip()
579+
self.clean = comments.strip(trimmed)
580+
self.has_comments = trimmed != self.clean
579581

580-
@functools.cached_property
581-
def clean(self):
582-
return self._strip_full() and self._strip_inline()
583582

584-
@property
585-
def has_comments(self):
586-
return self.strip() != self.clean
587-
588-
def _strip_inline(self):
589-
"""
590-
Search for the earliest prefix at the beginning of the line or following a space.
591-
"""
592-
matcher = re.compile(
593-
'|'.join(fr'(^|\s)({re.escape(prefix)})' for prefix in self.prefixes.inline)
594-
# match nothing if no prefixes
595-
or '(?!)'
583+
class _CommentSpec:
584+
def __init__(self, full_prefixes, inline_prefixes):
585+
full_patterns = (
586+
# prefix at the beginning of a line
587+
fr'^({re.escape(prefix)}).*'
588+
for prefix in full_prefixes
596589
)
597-
match = matcher.search(self)
598-
return self[:match.start() if match else None].strip()
590+
inline_patterns = (
591+
# prefix at the beginning of the line or following a space
592+
fr'(^|\s)({re.escape(prefix)}.*)'
593+
for prefix in inline_prefixes
594+
)
595+
self.pattern = re.compile('|'.join(itertools.chain(full_patterns, inline_patterns)))
596+
597+
def strip(self, text):
598+
return self.pattern.sub('', text).rstrip()
599599

600-
def _strip_full(self):
601-
return '' if any(map(self.strip().startswith, self.prefixes.full)) else True
600+
def wrap(self, text):
601+
return _Line(text, self)
602602

603603

604604
class RawConfigParser(MutableMapping):
@@ -667,10 +667,7 @@ def __init__(self, defaults=None, dict_type=_default_dict,
667667
else:
668668
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
669669
re.VERBOSE)
670-
self._prefixes = types.SimpleNamespace(
671-
full=tuple(comment_prefixes or ()),
672-
inline=tuple(inline_comment_prefixes or ()),
673-
)
670+
self._comments = _CommentSpec(comment_prefixes or (), inline_comment_prefixes or ())
674671
self._strict = strict
675672
self._allow_no_value = allow_no_value
676673
self._empty_lines_in_values = empty_lines_in_values
@@ -1066,7 +1063,6 @@ def _read(self, fp, fpname):
10661063
in an otherwise empty line or may be entered in lines holding values or
10671064
section names. Please note that comments get stripped off when reading configuration files.
10681065
"""
1069-
10701066
try:
10711067
ParsingError._raise_all(self._read_inner(fp, fpname))
10721068
finally:
@@ -1075,8 +1071,7 @@ def _read(self, fp, fpname):
10751071
def _read_inner(self, fp, fpname):
10761072
st = _ReadState()
10771073

1078-
Line = functools.partial(_Line, prefixes=self._prefixes)
1079-
for st.lineno, line in enumerate(map(Line, fp), start=1):
1074+
for st.lineno, line in enumerate(map(self._comments.wrap, fp), start=1):
10801075
if not line.clean:
10811076
if self._empty_lines_in_values:
10821077
# add empty line to the value, but only if there was no
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Restore :meth:`configparser.ConfigParser.read` performance.

0 commit comments

Comments
 (0)