90

Thanks to David Beazley's tweet, I've recently found out that the new Python 3.6 f-strings can also be nested:

>>> price = 478.23
>>> f"{f'${price:0.2f}':*>20s}"
'*************$478.23'

Or:

>>> x = 42
>>> f'''-{f"""*{f"+{f'.{x}.'}+"}*"""}-'''
'-*+.42.+*-'

While I am surprised that this is possible, I am missing on how practical is that, when would nesting f-strings be useful? What use cases can this cover?

Note: The PEP itself does not mention nesting f-strings, but there is a specific test case.

wim
  • 338,267
  • 99
  • 616
  • 750
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • 5
    Probably for the exact same purpose as nesting the older `str.format`: http://stackoverflow.com/questions/40245650/python-fixed-width-string-format-using-vars-or-dict – TigerhawkT3 Dec 19 '16 at 03:40
  • 1
    Another good one [here](http://stackoverflow.com/questions/32039239/python-string-formatting-old-vs-new-str-format). I'll leave the decision on whether to dupe hammer up to you. – TigerhawkT3 Dec 19 '16 at 03:43
  • 1
    @TigerhawkT3 thanks for the good examples! I am not sure these are direct duplicates but definitely relevant - as far as closing..I will accept whatever the community would decide. I also hope that may be there is something specific to f-strings here. We should probably give the topic time and a chance. – alecxe Dec 19 '16 at 03:55
  • 6
    Not sure if it's worth an answer, But pyramids are now easier to print in a single line `print("\n".join(f'{a:{a}<{a}}' for a in range(1,10)))` – Bhargav Rao Dec 25 '16 at 14:38
  • @BhargavRao wow, nice one! Thanks. – alecxe Dec 25 '16 at 15:24
  • 2
    I made this monstrosity for a timer a while ago: `f'''A timer has been set for {f"{hrs:02d}:{mins:02d}:{secs:02d}" if hrs > 0 else f"{f'{mins:02d}:{secs:02d}' if mins > 0 else f'{secs} seconds'}"}!'''` – kr8gz Nov 05 '20 at 17:06
  • 1
    Only for the sake of posterity, the link to the test case is no longer accurate (6 years later). I'm guessing it originally pointed at `test_nested_fstrings` which is now on line [1212](https://github.com/python/cpython/blob/main/Lib/test/test_fstring.py#L1212) – Marcel Wilson Aug 16 '23 at 19:22

15 Answers15

93

I don't think formatted string literals allowing nesting (by nesting, I take it to mean f'{f".."}') is a result of careful consideration of possible use cases, I'm more convinced it's just allowed in order for them to conform with their specification.

The specification states that they support full Python expressions* inside brackets. It's also stated that a formatted string literal is really just an expression that is evaluated at run-time (See here, and here). As a result, it only makes sense to allow a formatted string literal as the expression inside another formatted string literal, forbidding it would negate the full support for Python expressions.

The fact that you can't find use cases mentioned in the docs (and only find test cases in the test suite) is because this is probably a nice (side) effect of the implementation and not it's motivating use-case.


Actually, with three exceptions: An empty expression is not allowed, and a lambda expression must be surrounded by explicit parentheses. Nesting the a string with same quotes returns a syntax error.

silverwind
  • 3,296
  • 29
  • 31
Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • 2
    I am afraid you are right, completely agree. Out of upvotes for today - will come back tomorrow. Thanks. – alecxe Dec 19 '16 at 19:01
  • @alecxe I'm pretty sure some wacky things involving `f-string` nesting is going to pop-up in the wild at some point, though :-) – Dimitris Fasarakis Hilliard Dec 19 '16 at 19:25
  • Yep, just had a case where I needed to nest `f"…"` and used it much to my own delight One more reason that Python is utterly schnufte! – Jens Sep 19 '18 at 22:38
  • 1
    I can't put strings in quotes inside f-strings, let alone other f-strings. – rjurney Dec 20 '19 at 04:05
  • Three exceptions: Same-quote nesting does not work in Python, e.g. `f"{f"foo"}"` is a syntax error. The same works in JavaScript, so it's a Python shortcoming. – silverwind Feb 27 '23 at 16:20
18

I guess this is to pass formatting parameters in the same line and thus simplify f-strings usage.

For example:

>>> import decimal
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"
'result:      12.35'

Of course, it allows programmers to write absolutely unreadable code, but that's not the purpose :)

Eugene Lisitsky
  • 12,113
  • 5
  • 38
  • 59
  • 2
    Yes! `str.format` has always supported this for example `'{0:.{1}f}'.format(math.pi, 4)` is `'3.1416'`. If f-string couldn't support that, well, it would be lame. – wim Jan 05 '17 at 05:02
  • 6
    your example does not show a nested f-string, just nested curly braces. – Robert Nowak Sep 25 '19 at 09:06
9

I've actually just come across something similar (i think) and thought i'd share.

My specific case is a big dirty sql statement where i need to conditionally have some very different values but some fstrings are the same (and also used in other places).

Here is quick example of what i mean. The cols i'm selecting are same regardless (and also used in other queries elsewhere) but the table name depends on the group and is not such i could just do it in a loop.

Having to include mycols=mycols in str2 each time felt a little dirty when i have multiple such params.

I was not sure this would work but was happy it did. As to how pythonic its is i'm not really sure tbh.

mycols='col_a,col_b'

str1 = "select {mycols} from {mytable} where group='{mygroup}'".format(mycols=mycols,mytable='{mytable}',mygroup='{mygroup}')

group = 'group_b'

if group == 'group_a':
    str2 = str1.format(mytable='tbl1',mygroup=group)
elif group == 'group_b':
    str2 = str1.format(mytable='a_very_different_table_name',mygroup=group)

print(str2)
andrewm4894
  • 1,451
  • 4
  • 17
  • 37
  • 1
    Instead of substituting `{my_table}` for `my_table` in the `format` on line 3, you could just use `{{my_table}}` in the string literal. The `format` then converts double braces into single ones. So you'd have shorter code: `str1 = "select {mycols} from {{mytable}} where group='{{mygroup}}'".format(mycols=mycols)` – Arseny Dec 22 '20 at 22:37
7

Any basic use case is where you need a string to completely describe the object you want to put inside the f-string braces {}. For example, you need strings to index dictionaries.

So, I ended up using it in an ML project with code like:

scores = dict()
scores[f'{task}_accuracy'] = 100. * n_valid / n_total
print(f'{task}_accuracy: {scores[f"{task}_accuracy"]}')
Vishesh Agarwal
  • 101
  • 1
  • 5
3

Working on a pet project I got sidetracked by writing my own DB library. One thing I discovered was this:

>>> x = dict(a = 1, b = 2, d = 3)
>>> z = f"""
    UPDATE TABLE 
        bar 
    SET 
        {", ".join([ f'{k} = ?'     for k in x.keys() ])} """.strip()
>>> z
'UPDATE TABLE 
    bar 
SET 
    a = ?, b = ?, d = ?  '

I was also surprised by this and honestly I am not sure I would ever do something like this in production code BUT I have also said I wouldn't do a lot of other things in production code.

David
  • 17,673
  • 10
  • 68
  • 97
  • "I got sidetracked by writing my own DB library" ha ha ha :) and yes, this is interesting, and no, I would never use this in production either :) – Christopher Mahan Jan 11 '19 at 22:21
  • 1
    @ChristopherMahan I retired a few years ago so I have time to explore sometimes bad ideas. If you are curious https://github.com/devdave/dcdb There is a long list of features missing but that's fine as I have time to either implement them or snap and go back to sqlalchemy. – David Jan 12 '19 at 17:38
3

I found nesting to be useful when doing ternaries. Your opinion will vary on readability, but I found this one-liner very useful.

logger.info(f"No program name in subgroups file. Using {f'{prg_num} {prg_orig_date}' if not prg_name else prg_name}")

As such, my tests for nesting would be:

  • Is the value reused? (Variable for expression re-use)
  • Is the expression clear? (Not exceeding complexity)
Isaac D
  • 31
  • 1
3

I use it for formatting currencies. Given values like:

a=1.23
b=45.67

to format them with a leading $ and with the decimals aligned. e.g.

 $1.23
$45.67

formatting with a single f-string f"${value:5.2f}" you can get:

$ 1.23
$45.67

which is fine sometimes but not always. Nested f-strings f"${f'${value:.2f}':>6}" give you the exact format:

 $1.23
$45.67
2

A simple example of when it's useful, together with an example of implementation: sometimes the formatting is also a variable.

num = 3.1415
fmt = ".2f"
print(f"number is {num:{fmt}}")
alarrain
  • 51
  • 3
2

Nested f-strings vs. evaluated expressions in format specifiers

This question is about use-cases that would motivate using an f-string inside of some evaluated expression of an "outer" f-string.

This is different from the feature that allows evaluated expressions to appear within the format specifier of an f-string. This latter feature is extremely useful and somewhat relevant to this question since (1) it involves nested curly braces so it might be why people are looking at this post and (2) nested f-strings are allowed within the format specifier just as they are within other curly-expressions of an f-string.

F-string nesting can help with one-liners

Although certainly not the motivation for allowing nested f-strings, nesting may be helpful in obscure cases where you need or want a "one-liner" (e.g. lambda expressions, comprehensions, python -c command from the terminal). For example:

print('\n'.join([f"length of {x/3:g}{'.'*(11 - len(f'{x/3:g}'))}{len(f'{x/3:g}')}" for x in range(10)]))

If you do not need a one-liner, any syntactic nesting can be replaced by defining a variable previously and then using the variable name in the evaluated expression of the f-string (and in many if not most cases, the non-nested version would likely be more readable and easier to maintain; however it does require coming up with variable names):

for x in range(10):
    to_show = f"{x/3:g}"
    string_length = len(to_show)
    padding = '.' * (11 - string_length)
    print(f"length of {to_show}{padding}{string_length}")

Nested evaluated expressions (i.e. in the format specifier) are useful

In contrast to true f-string nesting, the related feature allowing evaluated expressions within the "format specifier" of an f-string can be extremely useful (as others have pointed out) for several reasons including:

  1. formatting can be shared across multiple f-strings or evaluated expressions
  2. formatting can include computed quantities that can vary from run to run

Here is an example that uses a nested evaluated expression, but not a nested f-string:

import random

results = [[i, *[random.random()] * 3] for i in range(10)]
format = "2.2f"

print("category,precision,recall,f1")
for cat, precision, recall, f1 in results:
    print(f"{cat},{precision:{format}},{recall:{format}},{f1:{format}}")

However, even this use of nesting can be replaced with more flexible (and maybe cleaner) code that does not require syntactic nesting:

import random

results = [[i, *[random.random()] * 3] for i in range(10)]
def format(x):
    return f"{x:2.2f}"

print("category,precision,recall,f1")
for cat, precision, recall, f1 in results:
    print(f"{cat},{format(precision)},{format(recall)},{format(f1)}")
teichert
  • 3,963
  • 1
  • 31
  • 37
2

The following nested f-string one-liner does a great job in constructing a command argument string

cmd_args = f"""{' '.join([f'--{key} {value}' for key, value in kwargs.items()])}"""

where the input {'a': 10, 'b': 20, 'c': 30, ....}

gets elegantly converted to --a 10 --b 20 --c 30 ... `

ushangman
  • 46
  • 4
  • How does that differ from the unnested version, `cmd_args = ' '.join(f'--{key} {value}' for key, value in kwargs.items())`? – rici Mar 01 '22 at 03:29
1

In F-string the open-paren & close-paren are reserved key characters. To use f-string to build json string you have to escape the parentheses characters. in your case only the outer parens.

f"\{f'${price:0.2f}':*>20s\}"

Steve
  • 905
  • 1
  • 8
  • 32
1

If some fancy formatting is needed, such nesting could be, perhaps, useful.

for n in range(10, 1000, 100):
    print(f"{f'n = {n:<3}':<15}| {f'|{n:>5}**2 = {n**2:<7_}'} |")
khaz
  • 369
  • 3
  • 12
1

Prior to Python 3.12, nesting f-strings was constrained by the limitations in string parsing. For example, f'{f""}' was valid whereas f'{f"{f''}"}' would lead to a SyntaxError. Consequently, one could only nest f-strings up to four times (using ', " and their respective triple quotes).

Since it seemed arbitrary, inconsistent and counterintuitive for the f-strings not to follow the usual Python syntax, with the introduction of Python 3.12, such limitations have been lifted, thereby making the sample expression f'{f"{f''}"}' valid. Other motivations for this change are further outlined in PEP 701.

Arn
  • 1,898
  • 12
  • 26
0

Selenium test verifying the text on a webpage reads a specific way depending on dynamic data input.

weeks: Literal[1,2,3,4,5,6] | None
rotation = f"Rotation:\n{f'{weeks} weeks' if weeks else 'Add rotation'}"
Marcel Wilson
  • 3,842
  • 1
  • 26
  • 55
-3

You could use it for dynamicism. For instance, say you have a variable set to the name of some function:

func = 'my_func'

Then you could write:

f"{f'{func}'()}" 

which would be equivalent to:

'{}'.format(locals()[func]()) 

or, equivalently:

'{}'.format(my_func())
Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
BallpointBen
  • 9,406
  • 1
  • 32
  • 62