Python f-string cheat sheets (2022)
157 points
2 days ago
| 12 comments
| fstring.help
| HN
th
2 days ago
[-]
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.

reply
lbourdages
2 days ago
[-]
Is it at all possible to use spaces as the thousands separator in Python f strings?

At the very least, your tool wasn't able to figure it out and hardcoded most of the number I wrote in.

reply
nickcw
2 days ago
[-]
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.

reply
ayhanfuat
2 days ago
[-]
> if you look at the cheat sheet, they are no longer simple.

Most of the formatting options in the cheat sheet have always been available with f-strings. They are part of the Format Specification Mini Language (https://docs.python.org/3/library/string.html#format-specifi...) and both string.format and f-strings use the same spec. f-strings have some additional capabilities like inline expressions and debugging/logging with `=`.

reply
disgruntledphd2
2 days ago
[-]
The equals addition is basically the best thing ever, I almost always use it for logging. Incredibly helpful (for me, at least).
reply
lynnharry
2 days ago
[-]
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.
reply
zahlman
2 days ago
[-]
"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).

reply
3abiton
2 days ago
[-]
I think complexity is a byproduct of flexibility. At least in this case, there is a beginner version.
reply
ilovetux
2 days ago
[-]
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...

reply
jonathaneunice
2 days ago
[-]
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.)

reply
somat
2 days ago
[-]
If i had to guess, the implicit access to all your vars feels gross to some, it's why I don't use them. I mean they are probably fine, like you said, puts the symbol right where you want it. It just never felt right to me. so I keep up the double dance of explicate format() calls.

It is probably some sort of deep seated printf() based trauma.

reply
danwills
1 day ago
[-]
I recently swapped from adding strings together (just what I've always done) to f-strings and I'm not looking back!

I even get to keep the idea of being able to read the vars in-place in the string which is certainly the last thing that I needed to be happy to use them! Full convert now!

reply
roenxi
2 days ago
[-]
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.

reply
cloudbonsai
2 days ago
[-]
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.
reply
jgtrosh
2 days ago
[-]
Yes, it's a generalisation of `%g` in f-string's ancestor printf(3). This is what people expect to find in formatting templates.
reply
zahlman
2 days ago
[-]
I don't know why people look at paragraphs of documentation that explain the exact results in strange edge cases (which have to exist because of the underlying complexity; in this example, Python can't change how IEEE-754 works, nor the original C printf specifier they're emulating) and conclude that this proves some strange and unexpected complexity was introduced.

When documentation isn't this thorough, people complain about that, too.

reply
cwilkes
2 days ago
[-]
Maybe should be called “iq-string” for Interview Question string.
reply
xavdid
2 days ago
[-]
Ah, these are great! f-strings are so powerful, but I can never remember the arcane little syntax. Definitely bookmarking this.
reply
asicsp
2 days ago
[-]
See also this quiz: https://fstrings.wtf/
reply
nomel
1 day ago
[-]
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.
reply
lenkite
2 days ago
[-]
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 ?
reply
hmry
2 days ago
[-]
In terms of format string syntax, Rust is extremely similar to Python (and C++'s new std::format is also similar). So in that sense it seems Python-style "{name:fmt}" has won. Though the others don't support arbitrary expressions inside of the string.

On the other hand, you have formatting as an expression that evaluates to a string like f"..." in Python, vs formatting as a set of functions that take the same input format but do various things like printf/snprintf/write!/format!/std::print/std::format... Here it seems Python's approach had too many drawbacks, considering they just added formatted templates t"..." that don't evaluate to a string.

If I were to design a new language, I would use a Python-like "{expr:fmt}" syntax for formatting, but I would make it evaluate to some sort of FormatTemplate object that code can inspect.

reply
f33d5173
2 days ago
[-]
> Here it seems Python's approach had too many drawbacks, considering they just added formatted templates t"..." that don't evaluate to a string.

That's not a fair characterization at all, since the plan was always to add something like the t"" strings. Having a version that immediately evaluates to a string is convenient, and adds very little complexity either at the implementation level or conceptually.

reply
zahlman
2 days ago
[-]
I don't think that was always the plan. Certainly the proposal has been tossed around for a while, but the overall historical evidence paints a different picture for me.
reply
pansa2
2 days ago
[-]
Can you get away with just f-strings? Python also has `string.format` for situations where the string needs to be built dynamically.

Are there languages which only have an f-string-like feature without a `string.format` equivalent?

reply
zahlman
2 days ago
[-]
t-strings with str.format-like limitations on what can be substituted in, except the t is actually an operator that applies at compile time when its argument is a literal and at runtime otherwise. And then f is another such operator that sugars over a standard library format(t '{...}') call.

(I've been thinking about this for a while, actually. Since PEP 751 was still in discussion.)

reply
jokoon
2 days ago
[-]
I use them and I discovered a few that I did not know
reply
TZubiri
2 days ago
[-]
Here's my cheat sheet:

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

reply
Sohcahtoa82
1 day ago
[-]
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.

reply
metalliqaz
2 days ago
[-]
Or even print("STRING", var, "STRING")

keep in mind that for long strings, each `+` creates a new string object (at least, it did in the 2.x days and I assume it hasn't changed)

reply
zahlman
2 days ago
[-]
It has indeed not changed. But practically speaking it doesn't matter much. The string implementation cheats by using resizable buffers internally for at least some purposes, while presenting an immutable-type interface. But regardless, a given line of code is going to have O(1) such additions; it's not remotely as bad as `for i in items: str += foo(i)`. (This should be done using `''.join` instead.)
reply
TZubiri
1 day ago
[-]
#Takes around 1 second s="a"*3000000000

#instant, does not consume twice as much memory. s+="a"

I don't know the internals, but certainly there's not a new string being created. Maybe if it exceeds capacity? Who cares at that point, it's python strings, not Matmuls in C.

reply
blueflow
2 days ago
[-]
I fail to see the purpose of f-strings if they end up as complex as printf formatting. Maybe use printf at this point?
reply
ForceBru
2 days ago
[-]
I think the purpose is to put variables directly in the string literal instead of calling `printf`. Looking at an f-string immediately tells you where each value will be located in the output. `printf` requires you to read the format string, then look at the arguments, then count where a particular value will be printed.
reply
lou1306
2 days ago
[-]
`printf`/`str.format` are also prone to nasty failures: if you forget a variable (e.g., `str.format("{} {} {}", 0, 1)`), you only find out when you crash with an IndexError at runtime.
reply
zahlman
2 days ago
[-]
I would expect that linters can pick up this sort of thing pretty easily.
reply
Hamuko
2 days ago
[-]
You don't really need to use any of these. Really the most useful one is f"{var=}" for us print() debuggers. But f"{var:#x}" is the same as f"{hex(var)}", so feel free to pick whatever you prefer.
reply
positr0n
1 day ago
[-]
Sometimes you need a formatted string outside of a printf content.
reply