154154import os
155155import re
156156import sys
157- import types
158157
159158__all__ = ("NoSectionError" , "DuplicateOptionError" , "DuplicateSectionError" ,
160159 "NoOptionError" , "InterpolationError" , "InterpolationDepthError" ,
@@ -570,35 +569,36 @@ def __init__(self):
570569
571570
572571class _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
604604class 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
0 commit comments