Learned a few tricks that I'm sure are buried on fstring.help somewhere (^ for centering, # for 0x/0b/0o prefixes, !a for ascii). I missed the nested f-strings question, because I've been stuck with 3.11 rules, where nested f-strings are still allowed but require different quote characters (e.g. print(f"{f'{{}}'}") would work). I guess this got cleaned up (along with a bunch of other restrictions like backslashes and newlines) in 3.12.
F-strings are great, but trying to remember the minute differences between string interpolation, old-style formatting with %, and new-style formatting with .format(), is sort of a headache, and there's cases where it's unavoidable to switch between them with some regularity (custom __format__ methods, templating strings, logging, etc). It's great that there's ergonomic new ways of doing things, which makes it all the more frustrating to regularly have to revert to older, less polished solutions.
Yeah I consider that one to be a trick question. I knew same-quote-style nested f-strings were coming, I just didn't know which version, and I still use the `f'{f"{}"}'` trick because I want my code to support "older" versions of python. One of my servers is still on 3.10. 3.11 won't be EOL until 2027.
One thing I keep not understanding is I see colleagues or AIs put f-strings in logger calls! Doesn't the logger do lazy interpolation? Doesn't this lose that nice feature?
I used to take this approach (didn’t worry about late interpolation for debug logs unless in a tight loop), but I was wrong and got bitten several times. Now I very strongly tend to favor lazy interpolation for logging.
The reason is that focusing on formatting CPU performance misses a bigger issue: memory thrashing.
There are a surprisingly large number of places in the average log-happy program where the arguments to the logger format string can be unexpectedly large in uncommon-but-not-astronomically-improbable situations.
When the amount of memory needed to construct the string to feed down into the logger (even if nothing is done with it due to log levels) is an order of magnitude or two bigger than the usual less-than-1kb log line, the performance cost of allocating and freeing that memory can be surprising—surprising enough to go from “only tight number crunching loops will notice overhead from logger formatting” to “a 5krps fast webhook HTTP handler that calls logger.debug a few times with the request payload just got 50% slower due to malloc thrashing”.
As said I default to deferred %. That leaves judgement for need vs cost of call vs frequency, etc. Also f-string is faster than others with own via instruction, more readable, less likely to throw a runtime error. Judgement call.
Bottom line, still a micro optimization on a modern machine in the vast majority of cases.
If you have a truly costly format op and still want to use fstring, look into .isEnabledFor().
F-strings are great, but trying to remember the minute differences between string interpolation, old-style formatting with %, and new-style formatting with .format(), is sort of a headache, and there's cases where it's unavoidable to switch between them with some regularity (custom __format__ methods, templating strings, logging, etc). It's great that there's ergonomic new ways of doing things, which makes it all the more frustrating to regularly have to revert to older, less polished solutions.