]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118761: Optimise import time for ``string`` (#132037)
authorAdam Turner <9087854+AA-Turner@users.noreply.github.com>
Tue, 8 Apr 2025 10:05:48 +0000 (11:05 +0100)
committerGitHub <noreply@github.com>
Tue, 8 Apr 2025 10:05:48 +0000 (10:05 +0000)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Lib/string.py
Misc/NEWS.d/next/Library/2025-04-03-01-35-02.gh-issue-118761.VQcj70.rst [new file with mode: 0644]

index c4f05c7223ce8a15f428f6d618533cf7579b00b4..eab5067c9b133ea1210efacd92eb5f69c2336236 100644 (file)
@@ -49,11 +49,18 @@ def capwords(s, sep=None):
 
 
 ####################################################################
-import re as _re
-from collections import ChainMap as _ChainMap
-
 _sentinel_dict = {}
 
+
+class _TemplatePattern:
+    # This descriptor is overwritten in ``Template._compile_pattern()``.
+    def __get__(self, instance, cls=None):
+        if cls is None:
+            return self
+        return cls._compile_pattern()
+_TemplatePattern = _TemplatePattern()
+
+
 class Template:
     """A string class for supporting $-substitutions."""
 
@@ -64,14 +71,21 @@ class Template:
     # See https://bugs.python.org/issue31672
     idpattern = r'(?a:[_a-z][_a-z0-9]*)'
     braceidpattern = None
-    flags = _re.IGNORECASE
+    flags = None  # default: re.IGNORECASE
+
+    pattern = _TemplatePattern  # use a descriptor to compile the pattern
 
     def __init_subclass__(cls):
         super().__init_subclass__()
-        if 'pattern' in cls.__dict__:
-            pattern = cls.pattern
-        else:
-            delim = _re.escape(cls.delimiter)
+        cls._compile_pattern()
+
+    @classmethod
+    def _compile_pattern(cls):
+        import re  # deferred import, for performance
+
+        pattern = cls.__dict__.get('pattern', _TemplatePattern)
+        if pattern is _TemplatePattern:
+            delim = re.escape(cls.delimiter)
             id = cls.idpattern
             bid = cls.braceidpattern or cls.idpattern
             pattern = fr"""
@@ -82,7 +96,10 @@ class Template:
               (?P<invalid>)             # Other ill-formed delimiter exprs
             )
             """
-        cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE)
+        if cls.flags is None:
+            cls.flags = re.IGNORECASE
+        pat = cls.pattern = re.compile(pattern, cls.flags | re.VERBOSE)
+        return pat
 
     def __init__(self, template):
         self.template = template
@@ -105,7 +122,8 @@ class Template:
         if mapping is _sentinel_dict:
             mapping = kws
         elif kws:
-            mapping = _ChainMap(kws, mapping)
+            from collections import ChainMap
+            mapping = ChainMap(kws, mapping)
         # Helper function for .sub()
         def convert(mo):
             # Check the most common path first.
@@ -124,7 +142,8 @@ class Template:
         if mapping is _sentinel_dict:
             mapping = kws
         elif kws:
-            mapping = _ChainMap(kws, mapping)
+            from collections import ChainMap
+            mapping = ChainMap(kws, mapping)
         # Helper function for .sub()
         def convert(mo):
             named = mo.group('named') or mo.group('braced')
@@ -170,10 +189,6 @@ class Template:
                     self.pattern)
         return ids
 
-# Initialize Template.pattern.  __init_subclass__() is automatically called
-# only for subclasses, not for the Template class itself.
-Template.__init_subclass__()
-
 
 ########################################################################
 # the Formatter class
diff --git a/Misc/NEWS.d/next/Library/2025-04-03-01-35-02.gh-issue-118761.VQcj70.rst b/Misc/NEWS.d/next/Library/2025-04-03-01-35-02.gh-issue-118761.VQcj70.rst
new file mode 100644 (file)
index 0000000..257ad7e
--- /dev/null
@@ -0,0 +1,2 @@
+Improve import times by up to 27x for the :mod:`string` module.
+Patch by Adam Turner.