@@ -49,11 +49,18 @@ def capwords(s, sep=None):
49
49
50
50
51
51
####################################################################
52
- import re as _re
53
- from collections import ChainMap as _ChainMap
54
-
55
52
_sentinel_dict = {}
56
53
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
+
57
64
class Template :
58
65
"""A string class for supporting $-substitutions."""
59
66
@@ -64,14 +71,21 @@ class Template:
64
71
# See https://bugs.python.org/issue31672
65
72
idpattern = r'(?a:[_a-z][_a-z0-9]*)'
66
73
braceidpattern = None
67
- flags = _re .IGNORECASE
74
+ flags = None # default: re.IGNORECASE
75
+
76
+ pattern = _TemplatePattern # use a descriptor to compile the pattern
68
77
69
78
def __init_subclass__ (cls ):
70
79
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 )
75
89
id = cls .idpattern
76
90
bid = cls .braceidpattern or cls .idpattern
77
91
pattern = fr"""
@@ -82,7 +96,10 @@ def __init_subclass__(cls):
82
96
(?P<invalid>) # Other ill-formed delimiter exprs
83
97
)
84
98
"""
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
86
103
87
104
def __init__ (self , template ):
88
105
self .template = template
@@ -105,7 +122,8 @@ def substitute(self, mapping=_sentinel_dict, /, **kws):
105
122
if mapping is _sentinel_dict :
106
123
mapping = kws
107
124
elif kws :
108
- mapping = _ChainMap (kws , mapping )
125
+ from collections import ChainMap
126
+ mapping = ChainMap (kws , mapping )
109
127
# Helper function for .sub()
110
128
def convert (mo ):
111
129
# Check the most common path first.
@@ -124,7 +142,8 @@ def safe_substitute(self, mapping=_sentinel_dict, /, **kws):
124
142
if mapping is _sentinel_dict :
125
143
mapping = kws
126
144
elif kws :
127
- mapping = _ChainMap (kws , mapping )
145
+ from collections import ChainMap
146
+ mapping = ChainMap (kws , mapping )
128
147
# Helper function for .sub()
129
148
def convert (mo ):
130
149
named = mo .group ('named' ) or mo .group ('braced' )
@@ -170,10 +189,6 @@ def get_identifiers(self):
170
189
self .pattern )
171
190
return ids
172
191
173
- # Initialize Template.pattern. __init_subclass__() is automatically called
174
- # only for subclasses, not for the Template class itself.
175
- Template .__init_subclass__ ()
176
-
177
192
178
193
########################################################################
179
194
# the Formatter class
0 commit comments