Skip to content

Commit ee36572

Browse files
gh-118761: Optimise import time for string (#132037)
Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 53908bd commit ee36572

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

Diff for: Lib/string.py

+30-15
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,18 @@ def capwords(s, sep=None):
4949

5050

5151
####################################################################
52-
import re as _re
53-
from collections import ChainMap as _ChainMap
54-
5552
_sentinel_dict = {}
5653

54+
55+
class _TemplatePattern:
56+
# This descriptor is overwritten in ``Template._compile_pattern()``.
57+
def __get__(self, instance, cls=None):
58+
if cls is None:
59+
return self
60+
return cls._compile_pattern()
61+
_TemplatePattern = _TemplatePattern()
62+
63+
5764
class Template:
5865
"""A string class for supporting $-substitutions."""
5966

@@ -64,14 +71,21 @@ class Template:
6471
# See https://bugs.python.org/issue31672
6572
idpattern = r'(?a:[_a-z][_a-z0-9]*)'
6673
braceidpattern = None
67-
flags = _re.IGNORECASE
74+
flags = None # default: re.IGNORECASE
75+
76+
pattern = _TemplatePattern # use a descriptor to compile the pattern
6877

6978
def __init_subclass__(cls):
7079
super().__init_subclass__()
71-
if 'pattern' in cls.__dict__:
72-
pattern = cls.pattern
73-
else:
74-
delim = _re.escape(cls.delimiter)
80+
cls._compile_pattern()
81+
82+
@classmethod
83+
def _compile_pattern(cls):
84+
import re # deferred import, for performance
85+
86+
pattern = cls.__dict__.get('pattern', _TemplatePattern)
87+
if pattern is _TemplatePattern:
88+
delim = re.escape(cls.delimiter)
7589
id = cls.idpattern
7690
bid = cls.braceidpattern or cls.idpattern
7791
pattern = fr"""
@@ -82,7 +96,10 @@ def __init_subclass__(cls):
8296
(?P<invalid>) # Other ill-formed delimiter exprs
8397
)
8498
"""
85-
cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE)
99+
if cls.flags is None:
100+
cls.flags = re.IGNORECASE
101+
pat = cls.pattern = re.compile(pattern, cls.flags | re.VERBOSE)
102+
return pat
86103

87104
def __init__(self, template):
88105
self.template = template
@@ -105,7 +122,8 @@ def substitute(self, mapping=_sentinel_dict, /, **kws):
105122
if mapping is _sentinel_dict:
106123
mapping = kws
107124
elif kws:
108-
mapping = _ChainMap(kws, mapping)
125+
from collections import ChainMap
126+
mapping = ChainMap(kws, mapping)
109127
# Helper function for .sub()
110128
def convert(mo):
111129
# Check the most common path first.
@@ -124,7 +142,8 @@ def safe_substitute(self, mapping=_sentinel_dict, /, **kws):
124142
if mapping is _sentinel_dict:
125143
mapping = kws
126144
elif kws:
127-
mapping = _ChainMap(kws, mapping)
145+
from collections import ChainMap
146+
mapping = ChainMap(kws, mapping)
128147
# Helper function for .sub()
129148
def convert(mo):
130149
named = mo.group('named') or mo.group('braced')
@@ -170,10 +189,6 @@ def get_identifiers(self):
170189
self.pattern)
171190
return ids
172191

173-
# Initialize Template.pattern. __init_subclass__() is automatically called
174-
# only for subclasses, not for the Template class itself.
175-
Template.__init_subclass__()
176-
177192

178193
########################################################################
179194
# the Formatter class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve import times by up to 27x for the :mod:`string` module.
2+
Patch by Adam Turner.

0 commit comments

Comments
 (0)