@@ -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+
5764class 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
0 commit comments