-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
gh-135676: Reword the f-string (and t-string) section #137469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
encukou
wants to merge
18
commits into
python:main
Choose a base branch
from
encukou:lex-analysis-fstrings
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d4381aa
WIP: String literals
encukou 80ad85c
Use correct Pygments lexer for plain text
encukou e44fa66
WIP
encukou 86bf94b
More WIP
encukou faf05a1
Byte strings, raw strings; f-string stub
encukou 687fe58
Remove outdated comment
encukou 9f9d29c
Fix ReST errors
encukou 1e0c84a
TMP
encukou e7b57b5
Continue with f-strings
encukou 3733e0c
Merge in the main branch
encukou d593940
Work on the f-string semantics
encukou 0d8a917
Work on f-strings
encukou 366ecfc
Merge in the main branch
encukou 5fdb129
Details & start on the formal grammar
encukou e88843c
Improve text on whitespace in f-string debug expressions
encukou 394f835
Merge in the main branch
encukou f2db8f9
Comment on the funkiness of the t-string grammar
encukou 6468a97
Adjust t-string docs: move evaluation rules out, add a note on grammar
encukou File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2528,6 +2528,8 @@ expression support in the :mod:`re` module). | |||||||||
single: : (colon); in formatted string literal | ||||||||||
single: = (equals); for help in debugging using string literals | ||||||||||
|
||||||||||
.. _stdtypes-fstrings: | ||||||||||
|
||||||||||
Formatted String Literals (f-strings) | ||||||||||
------------------------------------- | ||||||||||
|
||||||||||
|
@@ -2536,123 +2538,147 @@ Formatted String Literals (f-strings) | |||||||||
The :keyword:`await` and :keyword:`async for` can be used in expressions | ||||||||||
within f-strings. | ||||||||||
.. versionchanged:: 3.8 | ||||||||||
Added the debugging operator (``=``) | ||||||||||
Added the debug specifier (``=``) | ||||||||||
.. versionchanged:: 3.12 | ||||||||||
Many restrictions on expressions within f-strings have been removed. | ||||||||||
Notably, nested strings, comments, and backslashes are now permitted. | ||||||||||
|
||||||||||
An :dfn:`f-string` (formally a :dfn:`formatted string literal`) is | ||||||||||
a string literal that is prefixed with ``f`` or ``F``. | ||||||||||
This type of string literal allows embedding arbitrary Python expressions | ||||||||||
within *replacement fields*, which are delimited by curly brackets (``{}``). | ||||||||||
These expressions are evaluated at runtime, similarly to :meth:`str.format`, | ||||||||||
and are converted into regular :class:`str` objects. | ||||||||||
For example: | ||||||||||
This type of string literal allows embedding the results of arbitrary Python | ||||||||||
expressions within *replacement fields*, which are delimited by curly | ||||||||||
brackets (``{}``). | ||||||||||
Each replacement field must contain an expression, optionally followed by: | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
* a *debug specifier* -- an equal sign (``=``); | ||||||||||
* a *conversion specifier* -- ``!s``, ``!r`` or ``!a``; and/or | ||||||||||
* a *format specifier* prefixed with a colon (``:``). | ||||||||||
|
||||||||||
>>> who = 'nobody' | ||||||||||
>>> nationality = 'Spanish' | ||||||||||
>>> f'{who.title()} expects the {nationality} Inquisition!' | ||||||||||
'Nobody expects the Spanish Inquisition!' | ||||||||||
See the :ref:`Lexical Analysis section on f-strings <f-strings>` for details | ||||||||||
on the syntax of these fields. | ||||||||||
|
||||||||||
It is also possible to use a multi line f-string: | ||||||||||
Debug specifier | ||||||||||
^^^^^^^^^^^^^^^ | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
.. versionadded:: 3.8 | ||||||||||
|
||||||||||
>>> f'''This is a string | ||||||||||
... on two lines''' | ||||||||||
'This is a string\non two lines' | ||||||||||
If a debug specifier -- an equal sign (``=``) -- appears after the replacement | ||||||||||
field expression, the resulting f-string will contain the expression's source, | ||||||||||
the equal sign, and the value of the expression. | ||||||||||
This is often useful for debugging:: | ||||||||||
|
||||||||||
A single opening curly bracket, ``'{'``, marks a *replacement field* that | ||||||||||
can contain any Python expression: | ||||||||||
>>> number = 14.3 | ||||||||||
>>> 'number=14.3' | ||||||||||
number=14.3 | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
|
||||||||||
>>> nationality = 'Spanish' | ||||||||||
>>> f'The {nationality} Inquisition!' | ||||||||||
'The Spanish Inquisition!' | ||||||||||
Whitespace before, inside and after the expression, as well as whitespace | ||||||||||
after the equal sign, is significant --- it is retained in the result:: | ||||||||||
|
||||||||||
To include a literal ``{`` or ``}``, use a double bracket: | ||||||||||
>>> f'{ number - 4 = }' | ||||||||||
' number - 4 = 10.3' | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
|
||||||||||
>>> x = 42 | ||||||||||
>>> f'{{x}} is {x}' | ||||||||||
'{x} is 42' | ||||||||||
Conversion specifier | ||||||||||
^^^^^^^^^^^^^^^^^^^^ | ||||||||||
|
||||||||||
Functions can also be used, and :ref:`format specifiers <formatstrings>`: | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
|
||||||||||
>>> from math import sqrt | ||||||||||
>>> f'√2 \N{ALMOST EQUAL TO} {sqrt(2):.5f}' | ||||||||||
'√2 ≈ 1.41421' | ||||||||||
|
||||||||||
Any non-string expression is converted using :func:`str`, by default: | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
By default, the value of a replacement field expression is converted to | ||||||||||
string using :func:`str`:: | ||||||||||
Comment on lines
+2584
to
+2585
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
>>> from fractions import Fraction | ||||||||||
>>> f'{Fraction(1, 3)}' | ||||||||||
>>> one_third = Fraction(1, 3) | ||||||||||
>>> f'{one_third}' | ||||||||||
'1/3' | ||||||||||
|
||||||||||
To use an explicit conversion, use the ``!`` (exclamation mark) operator, | ||||||||||
followed by any of the valid formats, which are: | ||||||||||
When a debug specifier but no format specifier is used, the default conversion | ||||||||||
instead uses :func:`repr`:: | ||||||||||
|
||||||||||
========== ============== | ||||||||||
Conversion Meaning | ||||||||||
========== ============== | ||||||||||
``!a`` :func:`ascii` | ||||||||||
``!r`` :func:`repr` | ||||||||||
``!s`` :func:`str` | ||||||||||
========== ============== | ||||||||||
>>> f'{one_third = }' | ||||||||||
'one_third = Fraction(1, 3)' | ||||||||||
|
||||||||||
For example: | ||||||||||
The conversion can be specified explicitly using one of these specifiers: | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
* ``!s`` for :func:`str` | ||||||||||
* ``!r`` for :func:`repr` | ||||||||||
* ``!a`` for :func:`ascii` | ||||||||||
|
||||||||||
>>> from fractions import Fraction | ||||||||||
>>> f'{Fraction(1, 3)!s}' | ||||||||||
For example:: | ||||||||||
|
||||||||||
>>> str(one_third) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These example are duplicated, how about merging it all? |
||||||||||
'1/3' | ||||||||||
>>> f'{Fraction(1, 3)!r}' | ||||||||||
>>> repr(one_third) | ||||||||||
'Fraction(1, 3)' | ||||||||||
>>> question = '¿Dónde está el Presidente?' | ||||||||||
>>> print(f'{question!a}') | ||||||||||
'\xbfD\xf3nde est\xe1 el Presidente?' | ||||||||||
|
||||||||||
While debugging it may be helpful to see both the expression and its value, | ||||||||||
by using the equals sign (``=``) after the expression. | ||||||||||
This preserves spaces within the brackets, and can be used with a converter. | ||||||||||
By default, the debugging operator uses the :func:`repr` (``!r``) conversion. | ||||||||||
For example: | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
>>> f'{one_third!s} is {one_third!r}' | ||||||||||
'1/3 is Fraction(1, 3)' | ||||||||||
|
||||||||||
>>> from fractions import Fraction | ||||||||||
>>> calculation = Fraction(1, 3) | ||||||||||
>>> f'{calculation=}' | ||||||||||
'calculation=Fraction(1, 3)' | ||||||||||
>>> f'{calculation = }' | ||||||||||
'calculation = Fraction(1, 3)' | ||||||||||
>>> f'{calculation = !s}' | ||||||||||
'calculation = 1/3' | ||||||||||
|
||||||||||
Once the output has been evaluated, it can be formatted using a | ||||||||||
:ref:`format specifier <formatstrings>` following a colon (``':'``). | ||||||||||
After the expression has been evaluated, and possibly converted to a string, | ||||||||||
the :meth:`!__format__` method of the result is called with the format specifier, | ||||||||||
or the empty string if no format specifier is given. | ||||||||||
The formatted result is then used as the final value for the replacement field. | ||||||||||
For example: | ||||||||||
>>> string = "¡kočka 😸!" | ||||||||||
>>> ascii(string) | ||||||||||
"'\\xa1ko\\u010dka \\U0001f638!'" | ||||||||||
|
||||||||||
.. doctest:: | ||||||||||
>>> f'{string = !a}' | ||||||||||
"string = '\\xa1ko\\u010dka \\U0001f638!'" | ||||||||||
|
||||||||||
|
||||||||||
Format specifier | ||||||||||
^^^^^^^^^^^^^^^^ | ||||||||||
|
||||||||||
After the expression has been evaluated, and possibly converted using an | ||||||||||
explicit conversion specifier, it is formatted using the :func:`format` function. | ||||||||||
If the replacement field includes a *format specifier* introduced by a colon | ||||||||||
(``:``), the specifier is passed to :func:`!format` as the second argument. | ||||||||||
The result of :func:`!format` is then used as the final value for the | ||||||||||
replacement field. For example:: | ||||||||||
|
||||||||||
>>> from fractions import Fraction | ||||||||||
>>> f'{Fraction(1, 7):.6f}' | ||||||||||
'0.142857' | ||||||||||
>>> f'{Fraction(1, 7):_^+10}' | ||||||||||
'___+1/7___' | ||||||||||
>>> one_third = Fraction(1, 3) | ||||||||||
>>> f'{one_third:.6f}' | ||||||||||
'0.333333' | ||||||||||
>>> f'{one_third:_^+10}' | ||||||||||
'___+1/3___' | ||||||||||
>>> >>> f'{one_third!r:_^20}' | ||||||||||
'___Fraction(1, 3)___' | ||||||||||
>>> f'{one_third = :~>10}~' | ||||||||||
'one_third = ~~~~~~~1/3~' | ||||||||||
|
||||||||||
.. _stdtypes-tstrings: | ||||||||||
|
||||||||||
Template String Literals (t-strings) | ||||||||||
------------------------------------ | ||||||||||
|
||||||||||
An :dfn:`t-string` (formally a :dfn:`template string literal`) is | ||||||||||
a string literal that is prefixed with ``t`` or ``T``. | ||||||||||
|
||||||||||
These strings follow the same syntax and evaluation rules as | ||||||||||
:ref:`formatted string literals <stdtypes-fstrings>`, | ||||||||||
with for the following differences: | ||||||||||
|
||||||||||
* Rather than evaluating to a ``str`` object, template string literals evaluate | ||||||||||
to a :class:`string.templatelib.Template` object. | ||||||||||
|
||||||||||
* The :func:`format` protocol is not used. | ||||||||||
Instead, the format specifier and conversions (if any) are passed to | ||||||||||
a new :class:`~string.templatelib.Interpolation` object that is created | ||||||||||
for each evaluated expression. | ||||||||||
It is up to code that processes the resulting :class:`~string.templatelib.Template` | ||||||||||
object to decide how to handle format specifiers and conversions. | ||||||||||
|
||||||||||
* Format specifiers containing nested replacement fields are evaluated eagerly, | ||||||||||
prior to being passed to the :class:`~string.templatelib.Interpolation` object. | ||||||||||
For instance, an interpolation of the form ``{amount:.{precision}f}`` will | ||||||||||
evaluate the inner expression ``{precision}`` to determine the value of the | ||||||||||
``format_spec`` attribute. | ||||||||||
If ``precision`` were to be ``2``, the resulting format specifier | ||||||||||
would be ``'.2f'``. | ||||||||||
|
||||||||||
* When the equals sign ``'='`` is provided in an interpolation expression, | ||||||||||
the text of the expression is appended to the literal string that precedes | ||||||||||
the relevant interpolation. | ||||||||||
This includes the equals sign and any surrounding whitespace. | ||||||||||
The :class:`!Interpolation` instance for the expression will be created as | ||||||||||
normal, except that :attr:`~string.templatelib.Interpolation.conversion` will | ||||||||||
be set to '``r``' (:func:`repr`) by default. | ||||||||||
If an explicit conversion or format specifier are provided, | ||||||||||
this will override the default behaviour. | ||||||||||
|
||||||||||
|
||||||||||
.. _old-string-formatting: | ||||||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example is wrong, maybe this was intended?: