Skip to content

Commit 076d692

Browse files
CPython Devleopersyouknowone
authored andcommitted
Upgrade string from CPython 3.14.2
1 parent 346481d commit 076d692

File tree

2 files changed

+71
-22
lines changed

2 files changed

+71
-22
lines changed

Lib/string.py renamed to Lib/string/__init__.py

Lines changed: 38 additions & 22 deletions
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
@@ -212,19 +227,20 @@ def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
212227
# this is some markup, find the object and do
213228
# the formatting
214229

215-
# handle arg indexing when empty field_names are given.
216-
if field_name == '':
230+
# handle arg indexing when empty field first parts are given.
231+
field_first, _ = _string.formatter_field_name_split(field_name)
232+
if field_first == '':
217233
if auto_arg_index is False:
218234
raise ValueError('cannot switch from manual field '
219235
'specification to automatic field '
220236
'numbering')
221-
field_name = str(auto_arg_index)
237+
field_name = str(auto_arg_index) + field_name
222238
auto_arg_index += 1
223-
elif field_name.isdigit():
239+
elif isinstance(field_first, int):
224240
if auto_arg_index:
225-
raise ValueError('cannot switch from manual field '
226-
'specification to automatic field '
227-
'numbering')
241+
raise ValueError('cannot switch from automatic field '
242+
'numbering to manual field '
243+
'specification')
228244
# disable auto arg incrementing, if it gets
229245
# used later on, then an exception will be raised
230246
auto_arg_index = False

Lib/string/templatelib.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Support for template string literals (t-strings)."""
2+
3+
t = t"{0}"
4+
Template = type(t)
5+
Interpolation = type(t.interpolations[0])
6+
del t
7+
8+
def convert(obj, /, conversion):
9+
"""Convert *obj* using formatted string literal semantics."""
10+
if conversion is None:
11+
return obj
12+
if conversion == 'r':
13+
return repr(obj)
14+
if conversion == 's':
15+
return str(obj)
16+
if conversion == 'a':
17+
return ascii(obj)
18+
raise ValueError(f'invalid conversion specifier: {conversion}')
19+
20+
def _template_unpickle(*args):
21+
import itertools
22+
23+
if len(args) != 2:
24+
raise ValueError('Template expects tuple of length 2 to unpickle')
25+
26+
strings, interpolations = args
27+
parts = []
28+
for string, interpolation in itertools.zip_longest(strings, interpolations):
29+
if string is not None:
30+
parts.append(string)
31+
if interpolation is not None:
32+
parts.append(interpolation)
33+
return Template(*parts)

0 commit comments

Comments
 (0)