Nested f-strings

29,813

Solution 1

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 two exceptions: An empty expression is not allowed, and a lambda expression must be surrounded by explicit parentheses.

Solution 2

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 :)

Solution 3

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)

Solution 4

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"]}')

Solution 5

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.

Share:
29,813
alecxe
Author by

alecxe

"I am a soldier, at war with entropy itself" I am a Software Developer and generalist who is in love with the Python language and community. I greatly value clean and maintainable code, great software, but I know when I need to be a perfectionist and when it stands in a way of product delivery. I like to break things, to find new ways to break things, to solve hard problems, to put things under test and stress, and to have my mind blown by an interesting question. Some of my interests: Learning, Productivity, AI, Space Exploration, Internet of Things. "If you change the way you look at things, the things you look at change." - Wayne Dyer If you are looking for a different way to say "Thank you": Amazon wish list Pragmatic wish list Today I left my phone at home And went down to the sea. The sand was soft, the ocean glass, But I was still just me. Then pelicans in threes and fours, Glided by like dinosaurs, An otter basked upon its back, And dived to find another snack. The sun corpuscular and bright, Cast down a piercing shaft, And conjured an inspiring sight On glinting, bobbing craft. Two mermaids rose up from the reef, Out of the breaking waves. Their siren song was opium grief, Their faces from the grave. The mermaids asked a princely kiss To free them from their spell. I said to try a poet’s bliss. They shrugged and bid farewell. The sun grew dark and sinister, In unscheduled eclipse. As two eight-headed aliens Descended in their ships. They said the World was nice enough But didn’t like our star. And asked the way to Betelgeuse, If it wouldn’t be too far. Two whales breached far out to sea, And flew up to the sky, The crowd was busy frolicking, And didn’t ask me why. Today I left my phone at home, On the worst day, you’ll agree. If only I had pictures, If only you could see. Not everything was really there, I’m happy to confess, But I still have the memories, Worth more than tweets and stress. Today I left my phone at home, I had no shakes or sorrow. If that is what my mind can do, It stays at home tomorrow. Gavin Miller

Updated on July 05, 2022

Comments

  • alecxe
    alecxe almost 2 years

    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.

  • alecxe
    alecxe over 7 years
    I am afraid you are right, completely agree. Out of upvotes for today - will come back tomorrow. Thanks.
  • Dimitris Fasarakis Hilliard
    Dimitris Fasarakis Hilliard over 7 years
    @alecxe I'm pretty sure some wacky things involving f-string nesting is going to pop-up in the wild at some point, though :-)
  • wim
    wim over 7 years
    I tried it and it didn't work. TypeError: 'str' object is not callable
  • wim
    wim over 7 years
    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.
  • Jens
    Jens over 5 years
    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!
  • Mateen Ulhaq
    Mateen Ulhaq over 5 years
    This is not true. f'{func}' is a string. You can't call strings.
  • Mateen Ulhaq
    Mateen Ulhaq over 5 years
    If you're into unsafe hacks like these, you're probably looking for eval.
  • Lucas Meijer
    Lucas Meijer over 5 years
    "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 :)
  • David
    David over 5 years
    @ChristopherMahan I retired a few years ago so I have time to explore sometimes bad ideas. If you are curious 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.
  • Robert Nowak
    Robert Nowak over 4 years
    your example does not show a nested f-string, just nested curly braces.
  • Tom N Tech
    Tom N Tech over 4 years
    I can't put strings in quotes inside f-strings, let alone other f-strings.
  • Arseny
    Arseny over 3 years
    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)
  • rici
    rici about 2 years
    How does that differ from the unnested version, cmd_args = ' '.join(f'--{key} {value}' for key, value in kwargs.items())?