Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6020260

Browse files
lysnikolaoupicnixzAA-TurnerhugovkWingysam
authored
gh-132661: Implement PEP 750 (#132662)
Co-authored-by: Lysandros Nikolaou <lisandrosnik@gmail.com>Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>Co-authored-by: Wingy <git@wingysam.xyz>Co-authored-by: Koudai Aono <koxudaxi@gmail.com>Co-authored-by: Dave Peck <davepeck@gmail.com>Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>Co-authored-by: Paul Everitt <pauleveritt@me.com>Co-authored-by: sobolevn <mail@sobolevn.me>
1 parent5ea9010 commit6020260

File tree

81 files changed

+6148
-2193
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+6148
-2193
lines changed

‎.github/CODEOWNERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,9 @@ Lib/test/test__colorize.py @hugovk
320320

321321
# Fuzzing
322322
Modules/_xxtestfuzz/@ammaraskar
323+
324+
# t-strings
325+
**/*interpolationobject*@lysnikolaou
326+
**/*templateobject*@lysnikolaou
327+
**/*templatelib*@lysnikolaou
328+
**/*tstring*@lysnikolaou

‎Doc/library/token.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,41 @@ The token constants are:
131131

132132
The token string contains the closing quote(s).
133133

134+
..data::TSTRING_START
135+
136+
Token value used to indicate the beginning of a template string literal.
137+
138+
..impl-detail::
139+
140+
The token string includes the prefix and the opening quote(s), but none
141+
of the contents of the literal.
142+
143+
..versionadded::next
144+
145+
..data::TSTRING_MIDDLE
146+
147+
Token value used for literal text inside a template string literal
148+
including format specifications.
149+
150+
..impl-detail::
151+
152+
Replacement fields (that is, the non-literal parts of t-strings) use
153+
the same tokens as other expressions, and are delimited by
154+
:data:`LBRACE`,:data:`RBRACE`,:data:`EXCLAMATION` and:data:`COLON`
155+
tokens.
156+
157+
..versionadded::next
158+
159+
..data::TSTRING_END
160+
161+
Token value used to indicate the end of a template string literal.
162+
163+
..impl-detail::
164+
165+
The token string contains the closing quote(s).
166+
167+
..versionadded::next
168+
134169
..data::ENDMARKER
135170

136171
Token value that indicates the end of input.

‎Doc/whatsnew/3.14.rst

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Summary -- release highlights
6666
6767
*:ref:`PEP 649: deferred evaluation of annotations<whatsnew314-pep649>`
6868
*:ref:`PEP 741: Python Configuration C API<whatsnew314-pep741>`
69+
*:ref:`PEP 750: Template Strings<whatsnew314-pep750>`
6970
*:ref:`PEP 758: Allow except and except* expressions without parentheses<whatsnew314-pep758>`
7071
*:ref:`PEP 761: Discontinuation of PGP signatures<whatsnew314-pep761>`
7172
*:ref:`PEP 765: Disallow return/break/continue that exit a finally block<whatsnew314-pep765>`
@@ -92,6 +93,76 @@ If you encounter :exc:`NameError`\s or pickling errors coming out of
9293
New features
9394
============
9495

96+
.. _whatsnew314-pep750:
97+
98+
PEP 750: Template Strings
99+
-------------------------
100+
101+
Template string literals (t-strings) are a generalization of f-strings,
102+
using a ``t`` in place of the ``f`` prefix. Instead of evaluating
103+
to:class:`str`, t-strings evaluate to a new:class:`!string.templatelib.Template` type:
104+
105+
..code-block::python
106+
107+
from string.templatelibimport Template
108+
109+
name="World"
110+
template: Template= t"Hello{name}"
111+
112+
The template can then be combined with functions that operate on the template's
113+
structure to produce a:class:`str` or a string-like result.
114+
For example, sanitizing input:
115+
116+
..code-block::python
117+
118+
evil="<script>alert('evil')</script>"
119+
template= t"<p>{evil}</p>"
120+
assert html(template)=="<p>&lt;script&gt;alert('evil')&lt;/script&gt;</p>"
121+
122+
As another example, generating HTML attributes from data:
123+
124+
..code-block::python
125+
126+
attributes= {"src":"shrubbery.jpg","alt":"looks nice"}
127+
template= t"<img{attributes} />"
128+
assert html(template)=='<img src="shrubbery.jpg" alt="looks nice" class="looks-nice" />'
129+
130+
Unlike f-strings, the ``html`` function has access to template attributes
131+
containing the original information: static strings, interpolations, and values
132+
from the original scope. Unlike existing templating approaches, t-strings build
133+
from the well-known f-string syntax and rules. Template systems thus benefit
134+
from Python tooling as they are much closer to the Python language, syntax,
135+
scoping, and more.
136+
137+
Writing template handlers is straightforward:
138+
139+
..code-block::python
140+
141+
from string.templatelibimport Template, Interpolation
142+
143+
deflower_upper(template: Template) ->str:
144+
"""Render static parts lowercased and interpolations uppercased."""
145+
parts: list[str]= []
146+
for itemin template:
147+
ifisinstance(item, Interpolation):
148+
parts.append(str(item.value).upper())
149+
else:
150+
parts.append(item.lower())
151+
return"".join(parts)
152+
153+
name="world"
154+
assert lower_upper(t"HELLO{name}")=="hello WORLD"
155+
156+
With this in place, developers can write template systems to sanitize SQL, make
157+
safe shell operations, improve logging, tackle modern ideas in web development
158+
(HTML, CSS, and so on), and implement lightweight, custom business DSLs.
159+
160+
See:pep:`750` for more details.
161+
162+
(Contributed by Jim Baker, Guido van Rossum, Paul Everitt, Koudai Aono,
163+
Lysandros Nikolaou, Dave Peck, Adam Turner, Jelle Zijlstra, Bénédikt Tran,
164+
and Pablo Galindo Salgado in:gh:`132661`.)
165+
95166
.. _whatsnew314-pep768:
96167

97168
PEP 768: Safe external debugger interface for CPython

‎Grammar/Tokens

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ SOFT_KEYWORD
6262
FSTRING_START
6363
FSTRING_MIDDLE
6464
FSTRING_END
65+
TSTRING_START
66+
TSTRING_MIDDLE
67+
TSTRING_END
6568
COMMENT
6669
NL
6770
ERRORTOKEN

‎Grammar/python.gram

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ literal_pattern[pattern_ty]:
519519
literal_expr[expr_ty]:
520520
| signed_number !('+' | '-')
521521
| complex_number
522-
| strings
522+
|&(STRING|FSTRING_START|TSTRING_START)strings
523523
| 'None' { _PyAST_Constant(Py_None, NULL, EXTRA) }
524524
| 'True' { _PyAST_Constant(Py_True, NULL, EXTRA) }
525525
| 'False' { _PyAST_Constant(Py_False, NULL, EXTRA) }
@@ -859,7 +859,7 @@ atom[expr_ty]:
859859
| 'True' { _PyAST_Constant(Py_True, NULL, EXTRA) }
860860
| 'False' { _PyAST_Constant(Py_False, NULL, EXTRA) }
861861
| 'None' { _PyAST_Constant(Py_None, NULL, EXTRA) }
862-
| &(STRING|FSTRING_START) strings
862+
| &(STRING|FSTRING_START|TSTRING_START) strings
863863
| NUMBER
864864
| &'(' (tuple | group | genexp)
865865
| &'[' (list | listcomp)
@@ -935,7 +935,7 @@ fstring_middle[expr_ty]:
935935
fstring_replacement_field[expr_ty]:
936936
| '{' a=annotated_rhs debug_expr='='? conversion=[fstring_conversion] format=[fstring_full_format_spec] rbrace='}' {
937937
_PyPegen_formatted_value(p, a, debug_expr, conversion, format, rbrace, EXTRA) }
938-
|invalid_replacement_field
938+
|invalid_fstring_replacement_field
939939
fstring_conversion[ResultTokenWithMetadata*]:
940940
| conv_token="!" conv=NAME { _PyPegen_check_fstring_conversion(p, conv_token, conv) }
941941
fstring_full_format_spec[ResultTokenWithMetadata*]:
@@ -946,8 +946,27 @@ fstring_format_spec[expr_ty]:
946946
fstring[expr_ty]:
947947
| a=FSTRING_START b=fstring_middle* c=FSTRING_END { _PyPegen_joined_str(p, a, (asdl_expr_seq*)b, c) }
948948

949+
tstring_format_spec_replacement_field[expr_ty]:
950+
| '{' a=annotated_rhs debug_expr='='? conversion=[fstring_conversion] format=[tstring_full_format_spec] rbrace='}' {
951+
_PyPegen_formatted_value(p, a, debug_expr, conversion, format, rbrace, EXTRA) }
952+
| invalid_tstring_replacement_field
953+
tstring_format_spec[expr_ty]:
954+
| t=TSTRING_MIDDLE { _PyPegen_decoded_constant_from_token(p, t) }
955+
| tstring_format_spec_replacement_field
956+
tstring_full_format_spec[ResultTokenWithMetadata*]:
957+
| colon=':' spec=tstring_format_spec* { _PyPegen_setup_full_format_spec(p, colon, (asdl_expr_seq *) spec, EXTRA) }
958+
tstring_replacement_field[expr_ty]:
959+
| '{' a=annotated_rhs debug_expr='='? conversion=[fstring_conversion] format=[tstring_full_format_spec] rbrace='}' {
960+
_PyPegen_interpolation(p, a, debug_expr, conversion, format, rbrace, EXTRA) }
961+
| invalid_tstring_replacement_field
962+
tstring_middle[expr_ty]:
963+
| tstring_replacement_field
964+
| t=TSTRING_MIDDLE { _PyPegen_constant_from_token(p, t) }
965+
tstring[expr_ty] (memo):
966+
| a=TSTRING_START b=tstring_middle* c=TSTRING_END { _PyPegen_template_str(p, a, (asdl_expr_seq*)b, c) }
967+
949968
string[expr_ty]: s[Token*]=STRING { _PyPegen_constant_from_string(p, s) }
950-
strings[expr_ty] (memo): a[asdl_expr_seq*]=(fstring|string)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
969+
strings[expr_ty] (memo): a[asdl_expr_seq*]=(fstring|string|tstring)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
951970

952971
list[expr_ty]:
953972
| '[' a=[star_named_expressions] ']' { _PyAST_List(a, Load, EXTRA) }
@@ -1212,6 +1231,8 @@ invalid_expression:
12121231
RAISE_SYNTAX_ERROR_KNOWN_LOCATION (a, "expected expression before 'if', but statement is given") }
12131232
| a='lambda' [lambda_params] b=':' &FSTRING_MIDDLE {
12141233
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "f-string: lambda expressions are not allowed without parentheses") }
1234+
| a='lambda' [lambda_params] b=':' &TSTRING_MIDDLE {
1235+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "t-string: lambda expressions are not allowed without parentheses") }
12151236

12161237
invalid_named_expression(memo):
12171238
| a=expression ':=' expression {
@@ -1454,28 +1475,50 @@ invalid_starred_expression_unpacking:
14541475
invalid_starred_expression:
14551476
| '*' { RAISE_SYNTAX_ERROR("Invalid star expression") }
14561477

1457-
invalid_replacement_field:
1478+
invalid_fstring_replacement_field:
14581479
| '{' a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: valid expression required before '='") }
14591480
| '{' a='!' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: valid expression required before '!'") }
14601481
| '{' a=':' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: valid expression required before ':'") }
14611482
| '{' a='}' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "f-string: valid expression required before '}'") }
1462-
| '{' !annotated_rhs { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting a valid expression after '{'")}
1483+
| '{' !annotated_rhs { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting a valid expression after '{'")}
14631484
| '{' annotated_rhs !('=' | '!' | ':' | '}') {
14641485
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting '=', or '!', or ':', or '}'") }
14651486
| '{' annotated_rhs '=' !('!' | ':' | '}') {
14661487
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting '!', or ':', or '}'") }
1467-
| '{' annotated_rhs '='?invalid_conversion_character
1488+
| '{' annotated_rhs '='?invalid_fstring_conversion_character
14681489
| '{' annotated_rhs '='? ['!' NAME] !(':' | '}') {
14691490
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting ':' or '}'") }
14701491
| '{' annotated_rhs '='? ['!' NAME] ':' fstring_format_spec* !'}' {
14711492
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting '}', or format specs") }
14721493
| '{' annotated_rhs '='? ['!' NAME] !'}' {
14731494
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: expecting '}'") }
14741495

1475-
invalid_conversion_character:
1496+
invalid_fstring_conversion_character:
14761497
| '!' &(':' | '}') { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: missing conversion character") }
14771498
| '!' !NAME { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("f-string: invalid conversion character") }
14781499

1500+
invalid_tstring_replacement_field:
1501+
| '{' a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "t-string: valid expression required before '='") }
1502+
| '{' a='!' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "t-string: valid expression required before '!'") }
1503+
| '{' a=':' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "t-string: valid expression required before ':'") }
1504+
| '{' a='}' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "t-string: valid expression required before '}'") }
1505+
| '{' !annotated_rhs { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting a valid expression after '{'") }
1506+
| '{' annotated_rhs !('=' | '!' | ':' | '}') {
1507+
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting '=', or '!', or ':', or '}'") }
1508+
| '{' annotated_rhs '=' !('!' | ':' | '}') {
1509+
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting '!', or ':', or '}'") }
1510+
| '{' annotated_rhs '='? invalid_tstring_conversion_character
1511+
| '{' annotated_rhs '='? ['!' NAME] !(':' | '}') {
1512+
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting ':' or '}'") }
1513+
| '{' annotated_rhs '='? ['!' NAME] ':' fstring_format_spec* !'}' {
1514+
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting '}', or format specs") }
1515+
| '{' annotated_rhs '='? ['!' NAME] !'}' {
1516+
PyErr_Occurred() ? NULL : RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: expecting '}'") }
1517+
1518+
invalid_tstring_conversion_character:
1519+
| '!' &(':' | '}') { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: missing conversion character") }
1520+
| '!' !NAME { RAISE_SYNTAX_ERROR_ON_NEXT_TOKEN("t-string: invalid conversion character") }
1521+
14791522
invalid_arithmetic:
14801523
| sum ('+'|'-'|'*'|'/'|'%'|'//'|'@') a='not' b=inversion { RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "'not' after an operator must be parenthesized") }
14811524
invalid_factor:

‎Include/internal/pycore_ast.h

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎Include/internal/pycore_ast_state.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ struct _Py_global_strings {
371371
STRUCT_FOR_ID(consts)
372372
STRUCT_FOR_ID(context)
373373
STRUCT_FOR_ID(contravariant)
374+
STRUCT_FOR_ID(conversion)
374375
STRUCT_FOR_ID(cookie)
375376
STRUCT_FOR_ID(copy)
376377
STRUCT_FOR_ID(copyreg)
@@ -428,6 +429,7 @@ struct _Py_global_strings {
428429
STRUCT_FOR_ID(exception)
429430
STRUCT_FOR_ID(existing_file_name)
430431
STRUCT_FOR_ID(exp)
432+
STRUCT_FOR_ID(expression)
431433
STRUCT_FOR_ID(extend)
432434
STRUCT_FOR_ID(extra_tokens)
433435
STRUCT_FOR_ID(facility)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp