18 comments

[ 8.1 ms ] story [ 48.1 ms ] thread
Ah, these are great! f-strings are so powerful, but I can never remember the arcane little syntax. Definitely bookmarking this.
From the f-strings PEP 498

https://peps.python.org/pep-0498/

> This PEP is driven by the desire to have a simpler way to format strings in Python.

I think f-strings have become the thing they were trying to replace and they are now more complicated than the old % interpolation.

I still like f-strings and use them a lot but if you look at the cheat sheet, they are no longer simple.

I don't think "simple" here means lack of functions. It means more intuitive and simpler code, and easier curve of learning. And to me f-string is very simple.
I think complexity is a byproduct of flexibility. At least in this case, there is a beginner version.
"Simpler" here is at least partly comparing to explicit calls to the .format method, which was added all the way back in 2.6.

%-style interpolation supports many of these features, they just weren't as well known or discussed back then. The % style is also more complicated because of the weird edge cases (like trying to interpolate a single value which is a tuple).

This looks like a cheatsheet for writing a hard-to-read Python script. I don't know who gets karmic brownie points for a f"{string:>20}" field, but under most normal cases it'd be better to use rjust() directly and not force people to remember Yet Another DSL.

Once a reader could be reasonably expected to consult reference material while working out what a print() is doing something has gone wrong. This is the programmer equivalent of wearing too much makeup.

You haven't seen the full depth yet. Suppose that you encountered with this line:

    print(f"{n:.2g}")
What will it print? Here is the official explanation from https://docs.python.org/3.12/library/string.html#formatspec:

    g - General format. For a given precision p >= 1, this rounds the number to p significant digits and then formats the result in either fixed-point format or in scientific notation, depending on its magnitude. A precision of 0 is treated as equivalent to a precision of 1.

    The precise rules are as follows: suppose that the result formatted with presentation type 'e' and precision p-1 would have exponent exp. Then, if m <= exp < p, where m is -4 for floats and -6 for Decimals, the number is formatted with presentation type 'f' and precision p-1-exp. Otherwise, the number is formatted with presentation type 'e' and precision p-1. In both cases insignificant trailing zeros are removed from the significand, and the decimal point is also removed if there are no remaining digits following it, unless the '#' option is used.

    With no precision given, uses a precision of 6 significant digits for float. For Decimal, the coefficient of the result is formed from the coefficient digits of the value; scientific notation is used for values smaller than 1e-6 in absolute value and values where the place value of the least significant digit is larger than 1, and fixed-point notation is used otherwise.

    Positive and negative infinity, positive and negative zero, and nans, are formatted as inf, -inf, 0, -0 and nan respectively, regardless of the precision.
Make sense? You now should be able to see why it's called f-string.
I fail to see the purpose of f-strings if they end up as complex as printf formatting. Maybe use printf at this point?
Sometimes you need a formatted string outside of a printf content.
I love python f-strings. I dont use the format specifiers that this article points out.

Also, even though use in log messages is discouraged, I go ahead and use them. It will let me know if there is some code path where the proper variable is never set. This usually comes out through testing, especially during fuzzing so I guess it really only works because of my testing, otherwise it would come up during runtime...

Say if you were designing a new language and wanted to include string formatting as a feature. Would you personally choose Python f-strings or C-style format strings or Rust-style formats ?
I use them and I discovered a few that I did not know
I don't understand the f-string hate.

f-strings put the value in the output string exactly where the value should be. Massive win for contextual awareness, no need to count ...3, 4, okay what's the position 4 value over on the right, does it match up?? And they use classic Python string formatting commands, except the = operator which makes them even better with a "name the variable, show its value in a concise way" option. What's not to like?

(And if you don't like them, uh...they're not mandatory. Just don't use them.)

I created a tool over the weekend to guess the f-string you seek: http://pym.dev/format

I'm also the author of https://fstring.help/cheat/ (though not of the homepage) and I haven't yet linked back to that new tool. I was surprised to the cheat sheet here today but not the format guesser.

Here's my cheat sheet:

"STRING" + str(var) + "STRING"

I find "STRING {var} STRING" much more readable. Also, far easier to type. You don't even need to explicitly convert to a string first, it happens implicitly (Which, ironically, goes against the "Explicit is better than implicit" line in the Zen of Python).

But this page isn't about merely inserting a number/string into a string, it's about expressing how you want that number/string formatted. Maybe you only want the first two digits after a decimal. Maybe you want it to always be the same width and to pad with spaces or zeroes. Or any other ways of formatting.

And, don't forget you can pass everything after the ":" to a the `__format__(self, spec: str)` method, with its neat use cases, like unit conversion.