Yup, especially as you often need to access dict's with foo["field"] and you get a syntax error if the f-strings is also f"". JavaScript backtick syntax doesn't lead to this extra syntactic friction.
(I see from your carefully worded first sentence that you already knew this but still find it annoying, but I'll leave this here for any that aren't aware.)
I don't totally disagree. My company uses the black formatter, which does this[0]. There are flags to skip string formatting, but is frowned upon at my organization.
This is easily avoidable in python by using single quotes for the outer string and double quotes for the inner string, or vice versa. Or, use triple quotes for the outer string.
I'm surprised it works like that in Python. C# has similar interpolated strings but there it's legal to embed quotation marks within the curly brackets. Though I guess it's less necessary in Python since you can just use the other type of quote inside.
This is because of how f-strings were initially implemented: I piggy-backed them off of "regular" strings, then post-parsed inside that string. But the restriction is that the entire f-string (after the f) needs to be a valid regular Python string.
Since this:
"this is a "regular" string"
isn't valid, then neither is this:
f"dict lookup: {d["key"]}"
However, we've talked about changing f-string parsing from a "post-process regular strings" mode, to instead having the tokenizer and parser themselves aware of f-strings. When we do that, the f-string example above will work.
It can feel annoying sometimes, but it's also nice that it prevents you from getting overly complicated with your string interpolations. Human readability is highly valued in Python culture, and things start getting hard for humans to quickly parse when you allow arbitrarily complex interpolation expressions. Assigning your complicated expression to a well-named variable first will usually make your code more readable, even if it's slightly more verbose.
I agree it's not the most beautiful, but it was the best choice given the requirements. Basically, it had to be explicit, concise, use unused ASCII syntax, and not be too cryptic or incompatible. Other quote types or backticks were not available or an option.
(I spent a lot of time thinking about it, and put the idea forward on python-ideas. Though string interpolation is nothing new of course.)
I think you are right to prefer JS's backtick notation, but not in your reason.
The main problem with python's f" syntax is that it's just a very complex, non-extensible hack whereas in javascript you can prefix a tag to specify how the interpolation happens.
Instead of this, f-strings have a lot of hardcoded and often broken behavior which can't be fixed let alone customized. For example, you can specify a field width, but it will not even work properly even in a fixed-width context, because of quite common things like emojis or CJK or combining characters don't have their width computed correctly.
Yes. Python in general has gone from being “executable pseudo code” to a series of complex language design trade offs largely because it had failed to grasp the right abstractions in its evolution. Backtick functions in JS are so powerfully clever and basically impossible to recreate in Python.
Let’s go all the way back to `with`. What does it do? It encapsulates try/finally blocks and only try/finally. That is a weirdly specific thing to encapsulate. Why did they do that? Well, it turns out that making an anonymous block syntax that is compatible with Python’s grammar is hard, so you can’t have the equivalent of this JavaScript:
WithOpen(filename, f => {
// block
})
Because blocks aren’t viable, problems have to be solved on a case-by-case basis, which is hard to do well and leaves gaps.
Apart from somewhat negating the main advantage of f-strings (relative conciseness) it does not allow you take into account surrounding string context and of course will not fix problems when you have values that contain objects that already have __format__ defined. Also, python being python, you now slowed everything down by an order of magnitude.
I would personally be quite against that. That's a pretty large breaking change, with no clear benefit. It doesn't make the feature more discoverable, since you still need to know that using "{}" will cause interpolation. I'd say net zero effect on readability; mostly just individual preference. And potential negative impact on performance.
There would be a __future__ import to turn it on, at least for some number of releases. But yeah, I'm not sure it should ever be the default behavior. It makes most sense to me as a per-module opt-in, but then a __future__ import doesn't make sense. And we've resisted having per-module pragmas, so maybe it's dead in the water.
I think that would be a pretty serious harm to Python as a language. When I was learning Python, I was using PHP, which has $ interpolation for double quoted strings but not single quotes. I didn’t understand how PHP worked at all and it really confused me. In retrospect, I don’t know why it was so hard to wrap my head around, but it was. Learning Python was liberating because I knew exactly when and how interpolation would take place. The decision not to follow the lead of Perl and Bash and auto-interpolate was made deliberately by Guido many years ago. It would be a shame to go back on that decision now.
> I've put together a proposal to have all strings be f-strings by default.
Please don't do that. Seriously.
There is this trend in the Python community of blurring the line between a plain string and a formatting operation on that string, but there is a fundamental difference between the two. Instead of trying to ignore that difference, the language should make the difference explicit. "Explicit is better than implicit" is one of the things I like so much about Python. Please don't throw that away.
There is and should be a clear and fundamental difference between a plain string and a string formatting operation. The plain string is clearly the more fundamental of the two, so it should be the default.
And then there's the whole issue of breaking code with such a change.
Again, please don't do it. Don't change the language in such a fundamental way.
Won't happen. This would be a breaking change that would break pretty much every usage of `str.format`. Releasing a new incompatible version of a language is unlikely considering Python 2 to Python 3 migration mess.
I quite like the idea of using double-quotes for format strings and single-quotes for normal strings.
This isn’t sufficient on its own, though - what about raw strings? Perhaps use “” for format strings, ‘’ for raw strings and get rid of normal strings altogether?
> However, once you start using several parameters and longer strings, your code will quickly become much less easily readable [...] `"Hello, %s %s. You are %s. You are a %s ...`
Minor correction: % formatting allows specifying names for dictionary keys too:
In [1]: "%(a)s and %(b)s" % {"a":"A", "b":"B"}
Out[1]: 'A and B'
In fact I had never switched to using str.format as it just never added enough benefit for me over the old %-based formatting.
But I do like f-strings and am using them quite a bit. Calling functions or methods inside f-strings is something I had no idea was possible. That is pretty neat:
>>> name = "Eric Idle"
>>> f"{to_lowercase(name)} is funny."
'eric idle is funny.'
The article demonstrates both methods and functions. I just pasted the function example, but without the showing the function definition itself as it's fairly obvious :-)
I have mixed feelings about code inside f-strings. It seems like a bit of a slippery slope towards losing track of what's happening. I have been tempted to write things like below because I really just need it for the print.
f"Info print for item {hex(do_some_parsing(do_some_conversion(value)))}"
Which runs perfectly fine, but at least on my editor syntax highlighting stops working as it is all part of the string. And with that it becomes increasingly hard to work out what is actually being called. Now I resolved myself to pretty much just have one function call and no nesting. Things like f"{str.lower()}" or f"{hex(value)}" are great and I use them a lot.
I don’t think it’ll be an issue. This isn’t exactly new ground. We know the results from other languages. There might be some such abuse, but there’s a lot of other ways to abuse expressions and they don’t happen often because it’s just not worth it.
I have mixed feelings about just grabbing variables inside strings. Prior to this I had a coworker that really liked using the convention:
"{value}".format(**locals())
I thought it was clever at first, but this made refactoring really hard because you would move around these strings or variables and lose track of what's happening. Maybe IDEs are better at helping with this in recent years?
If I apply a bunch of functions to a variable before printing I tend to use .format() and do it inside the format call, e.g “{}”.format(a.lower().upper())
Something just feels more right about that. Maybe it is the code highlighting that works better.
PyCharm/intellij display the “code” part of f-strings similar to how a variable declaration or function call would be. I’m sure it’s possible for any other editors out there to apply some nicer formatting to f-strings as well.
I think it’s nice that there is only one escape character. You check for ‘\’. If it’s there, switch on the next character checking for ‘n’, ‘t’, ‘r’ or ‘(‘, defaulting to appending the unmodified character to the output.
I think that might not only be simpler, but also be slightly faster than having having two different escapes would be.
That doesn't work (easily) in a function as eval only has the global context, not access to local variables (although you can do some hackery to make it work)
When it comes to string formatting I have recently found my absolute favourite utility of all time: Scheme's SRFI-166. It is a combinator based string formatting utility that is user extensible (with verbosity worthy of being in every scheme!): https://srfi.schemers.org/srfi-166/srfi-166.html
This is super minor but I really wish f-string formatting were the default behavior for `print` or that there were a `printf` command just for it. Typing that extra f character before the quotes is just annoying enough
As a novice Python scripter, I felt that way for about 6 months. Then grumbled but accepted why making print a function was a good thing. Then spent another year frequently forgetting the brackets. Then another 6 months occasionally forgetting the brackets. Now, about 2 years after I converted to Python3, I finally have fully accepted that having print as a function is a good universal thing. At the very least, having an "end=" and "sep=" option eliminated 10,000 newbie python questions a year.
Elixir heredocs do this by default. Plus you can prepend it with sigils to activate string macros. Here I use it to inject content into another programming language embedded within Elixir (lower case ~z allows interpolation, upper case ~Z does not):
Thank you! This was literally what I opened the article for (I'd seen the f syntax previously but hadn't paid much attention) and it's conspicuously missing!
I love this - didn't realize it was a thing at all! If anyone is curious, this works for any type in Python that implements __format__ ({now:%m/%d/%Y} is equivalent to {now.__format__('%m/%d/%Y')}), and you can (ostensibly) make the format string mean whatever you want.
I didnt know format_map existed, but it looks like it is not in python 2 so it wouldn't have helped in that use case. Still, thanks for pointing that out. I love learning about new minor conveniences in Python.
I saw this a few years ago and thought it was cool at first, but I had huge problems when refactoring that code. I would miss variables used in strings when I would rename, move, or delete things. Have you had this issue? I imagine an IDE that could grok it would have helped.
> But for f-strings, it has full support for the "inline" variables.
Is this complete (meaning: it also includes more complicated expressions, referencing these variables)?
I'm asking, because some Python development tools are failing in doing it right. This leads to annoying reliability issues for refactorings (even for simple renames). Currently, I have the problem with Wing IDE.
As far as I know yes. It knows that the "usage" of that variable in an f-string is an actual reference to the variable vs just part of the string with the same set of characters.
Not sure what you mean by complicated expressions? If you mean the variable is referenced in a complicated statement with calcs or as part of a ternary operator, etc... AFAIK they are all supported. It even understands scope. So it won't go outside of the current scope where the variable is defined.
PyCharm's refactoring is very neat. In certain instances it even "refactors" relative paths inside a string if you move the file being referenced. Moving files around is intelligent and all imports get fixed. It has neat naming-convention renaming (myVariable->my_variable). Lots of nice little quality-of-life features, which is why I think it is the best Python IDE out there atm. Give it a whirl, it's got a free community edition that is near 100% functional!
Black is hardly the industry-wide standard, so I don't see why they would embed it into their IDE? Either way, the IDE has all the sufficient tools to enable me to easily call the black formatter along with pylint, pep8, flake8 and mypy. I have all of them setup in my IDE and trigger them with a right click command, a shortcut button, or automatically on save (if I wanted to).
F-strings are great. The only downside I’ve found is they are incompatible with the stdlib logging framework’s deferred string interpolation feature (using %s style string interpolation), which can give you a nice performance boost if you make lots of expensive debug logs for tests, but run at info level in production.
That said most apps probably won’t see much of a difference in perf between the two approaches.
f-strings are so much faster that I've seen an increase in speed over %-formatting for logging. If there's even a 10% chance that a string will be actually logged (and therefore %-formatting will take place), I'll replace it with an f-string and not care about the deferred formatting.
Something I find useful, and perhaps isn't widely known is that you can pop curly braces into the format part of an f-string to have dynamic formatting:
value = 5/6
precision = 2
print(f"{value:.{precision}f}")
> 0.83
Upon reading the title, I was not enthusiastic. Yet another way to format strings? What's wrong with String.format()? Get off my lawn!
However upon reading the article, it does feel more ergonomic to prepend your string literal with an 'f', and have it automagically reach out into the surrounding scope for the variables to format into the string.
I do worry a bit however about formatting not just expressions, but being able to call other code. Yes, that could be quite convenient for keeping the code concise and avoiding the kind of verbosity that confuses. However, I could also see someone forgetting to sanitize user-input data, and combined with the f-string, creating a remote arbitrary code-execution vulnerability. Sure, an attacker can't immediately start scribbling on memory in Python, there's more work he has to do first. But, he possibly could hijack the Python process to run whatever Python code he wanted.
This was just an example to show what it does, I normally wouldn't have \h in a string. I use this to store lists of regular expressions in aws dynamodb list type and to convert them back to raw strings when used.
A wart is the standard logging module, which uses %-style strings. For example:
log.debug("Unexpected, got %r", (got,))
By doing it this way, it can defer rendering the string until it is actually needed, which is (assumed to be) a win when the logging module is normally just going to toss the low level messages away rather than emit them anywhere.
I think you lose this optimization if you use f-strings. I don't think rendering of the string can be lazy, deferred until actually needed.
Yes, I was asked by a Junior if they should always use f strings now. There are still a couple situations where you need format, such as when you’re formatting a variable instead of a literal.
Also, shoutout to Template for untrusted strings. It’s surprisingly unknown. Most people suggest replace or a custom format function.
Also, remember that the first argument to the logging functions is considered to be a format string. So using f-strings is not just less efficient, it is also not robust, unless you do:
logging.debug("%s", f"Unexpected, got {got}")
because otherwise, if the format string is the first/only argument, and `got` unexpectedly contain `%`, an exception will be raised due to a missing format parameter.
(Also, for the logging functions, you don't pass a tuple, just a normal series of arguments.)
As the sibling comment points out, it actually doesn't work that way. The first parameter is only treated as a format string _if_ there are arguments. If not, it is treated as a literal string: https://github.com/python/cpython/blob/dcea78ff53d02733ac598...
logging.Formatter defaults to working with %-style strings but it it can be configured to use the {}-style of str.format. (As of 3.2)
You still miss out on the scoping of f-strings, and embedding function calls. But passing around what amounts to an "eval" expression is a footgun that should obviously be frowned upon.
There's also an option for $-style Template strings. But I can't recall when I've ever seen someone use that.
> In Python 3.2, the Formatter gained a style keyword parameter which, while defaulting to % for backward compatibility, allowed the specification of { or $ to support the formatting approaches supported by str.format() and string.Template. Note that this governs the formatting of logging messages for final output to logs, and is completely orthogonal to how an individual logging message is constructed.
The style option only applies to the outer format applied by the formatter (timestamp, level, file, line, etc), it does not apply to the logging calls.
From reading the comments in the discussion here, I'm curious why the f-string syntax is so all-over-the-place?
- f"{foo}" uses the default format
- f"{foo!r}" uses repr to format
- f"{foo=}" uses a special-cased debugging introspection
- f"{foo:formatstr}" uses the default format with a custom format string
- f"{foo:{bar}}" uses a dynamic format string
It seems like `!r` and `=` should have been implemented with `:r` and `:=` instead?
!r is one of a class of conversion flags, others are !a which uses ascii(), !s which uses str().
“=” in the format-spec (the thing that comes after the “:”) is the numeric padding alignment specifier (other alignment specifiers are left “<”, right “>”, and center “^”.)
The `=` you pointed out is a special case, however, and not part of the alignment specification. Sadly, this means that the above snippet is not as nicely formatted as I'd like. Still, it does seem useful for these cases:
You really have to understand how it's translated into actual code, and of course it's Python, so there's some legacy syntax to take into account. A `:` just means call format(lhs, rhs), for example.
You can also actually combine those syntaxes - I often use f"{foo:%Y-%m-%d!r}" to get a quoted, properly escaped date in a specific format, for example.
ES6 spec for template literals, while not unique as far as I know, has really been the most intuitive method for templating strings that I’ve seen. It’s a bit implicit so it probably wouldn’t work for Python (in this case, % formatting wins).
Some experiences of counterparts from languages I've used, although it's like bike shedding but it's still user experience. It would be user-hostile if not carefully designed:
Ruby: Good but there are too many kinds of syntax. Use double quote to interpolate which need to escape a lot. And single quote doesn't interpolate, which makes many code bases very mixed and merging code is annoying. I wonder if they swapped them in the first place would it make the mixed thing better because it's easier to settle on single quote?
C#: Support multiline, still awkward because it uses double quote.
JavaScript: Simple, intuitive yet extensible. It's pretty useful because in JavaScript world embedded languages are very common (CSS, HTML etc). And I find ${} is slightly better than {} in C# and Scala because of the escaping. The community is pretty much settled with single quote, and only use back tick when interpolating so it suck less than Ruby.
Scala: Extensible. Very thoughtful and cool, you can indent and strip margin. It's a little bit cumbersome to write multiline when it's space sensitive in JavaScript
Elixir: Extremely extensible, the Sigil is not only for strings but also for other syntax.
Clojure: It's weird that I put Clojure here, yet I find (str "Hello" name "!") is not far from dedicated interpolation syntax. S-expr, simplicity and all that.
I would say Clojure's is good enough for me, but JavaScript is pretty awesome already - maybe it would be perfect if it can strip margin as well.
The issue with f-strings is that they can't be used for i18n. AFAIK, it is impossible to make `_(f"Hello, {name}")` work - at least without a preprocessor or very arcane bytecode manipulation magic. This is unlike much uglier but common and well-supported `_("Hello, %(name)s") % {"name": name}`.
In the examples I've used `_` as an alias for a `gettext` call - I believe this is a common practice to do something like `from django.utils.translation import ugettext as _`.
A key feature to the recent JS and C# implementations of string interpretation ("template strings") was that both provide a way to pass a template string to a function in a deconstructed form (an array of string parts, and an array of the strings expected to fill their holes). In C# it's a static type expectation (if you cast to a string you get the final formatted string, if you request the FormattableString you get the full deconstructed meta-object) and in JS it's a special function call signature (`` is the template string delimiter and a function that takes the deconstructed parts of a template string is prefixed functionname`example string`).
This bit of additional functionality in both JS and C# makes their interpolated strings/template literals extremely useful for i18n (and other uses such as query parameterization and injection attack avoidance).
I'm surprised that in comparing the efforts of other languages Python didn't include such functionality in the f-strings PEP. I wonder if such a feature could be added in a non-disruptive way. Maybe a meta-property like a __format_parts__ or something?
146 comments
[ 2.7 ms ] story [ 199 ms ] threadOther than that, I agree, much better than % and less verbose than .format.
It's just the f" or f' that doesn't sit right with me
[0] https://github.com/psf/black/blob/master/docs/the_black_code...
Since this:
"this is a "regular" string"
isn't valid, then neither is this:
f"dict lookup: {d["key"]}"
However, we've talked about changing f-string parsing from a "post-process regular strings" mode, to instead having the tokenizer and parser themselves aware of f-strings. When we do that, the f-string example above will work.
(edit for formatting)
(I spent a lot of time thinking about it, and put the idea forward on python-ideas. Though string interpolation is nothing new of course.)
The main problem with python's f" syntax is that it's just a very complex, non-extensible hack whereas in javascript you can prefix a tag to specify how the interpolation happens.
Instead of this, f-strings have a lot of hardcoded and often broken behavior which can't be fixed let alone customized. For example, you can specify a field width, but it will not even work properly even in a fixed-width context, because of quite common things like emojis or CJK or combining characters don't have their width computed correctly.
I'm not sure I expect it to handle every Unicode nuance either, when there are modules in the stdlib for that.
Agree with this 100%.
> because it had failed to grasp the right abstractions in its evolution.
I'd be interested to know what you think the language's designers should have done differently?
They are customizable on an object-to-be-formatted basis (__format__ special method).
Then you could drop the "f" prefix. I also propose adding a "p" prefix (for "plain") to remove the f-string parsing behavior.
I talked about it at the last Python core sprint, and it only got lukewarm support, so I'm not sure I'm going to pursue it.
There's a hack for that (no apologies for the self promotion; the project is a just funnin')
https://github.com/boothby/dissert
F-strings certainly seem like opt-in behavior to me.
Please, no.
I mean, it would be a breaking change, which needs a really compelling case, will make all introductory material wrong, and...offers what benefit?
Please don't do that. Seriously.
There is this trend in the Python community of blurring the line between a plain string and a formatting operation on that string, but there is a fundamental difference between the two. Instead of trying to ignore that difference, the language should make the difference explicit. "Explicit is better than implicit" is one of the things I like so much about Python. Please don't throw that away.
There is and should be a clear and fundamental difference between a plain string and a string formatting operation. The plain string is clearly the more fundamental of the two, so it should be the default.
And then there's the whole issue of breaking code with such a change.
Again, please don't do it. Don't change the language in such a fundamental way.
This isn’t sufficient on its own, though - what about raw strings? Perhaps use “” for format strings, ‘’ for raw strings and get rid of normal strings altogether?
https://docs.python.org/3/whatsnew/3.8.html#f-strings-suppor...
JFYI meme style responses are frowned upon at HN. That's why you get downvoted.
A much better rephrase of the above would be something along the lines of:
PHP/Javascript/.. also have this feature since ver X. It's main difference/advantage/disadvantage is ...
Minor correction: % formatting allows specifying names for dictionary keys too:
In fact I had never switched to using str.format as it just never added enough benefit for me over the old %-based formatting.But I do like f-strings and am using them quite a bit. Calling functions or methods inside f-strings is something I had no idea was possible. That is pretty neat:
Something just feels more right about that. Maybe it is the code highlighting that works better.
Which is usually used for escaping characters. Like, \n for new line.
So, you may end up with code like:
I think it’s nice that there is only one escape character. You check for ‘\’. If it’s there, switch on the next character checking for ‘n’, ‘t’, ‘r’ or ‘(‘, defaulting to appending the unmodified character to the output.
I think that might not only be simpler, but also be slightly faster than having having two different escapes would be.
I wrote a simple Awk script to convert Python with f-string syntax to syntax without it.
(The program had a function to do a more or less proper lexical analysis job; not some flimsy regex substitution hack.)
While I know why building f strings is forbidden (they can run arbitrary code), I'm happy to take the risk.
It was a beautiful thing and made it possible to write the same "hello world" program in Python and BASIC:
https://pyfound.blogspot.com/2020/04/all-strings-become-f-st...
Squiggly heredoc is something I am unaware of in any other general programming language.
https://www.youtube.com/watch?v=fIxNEILcNGM&t=25m50s
https://twitter.com/mariatta/status/1351359518316216321
In Python 2 I used to use .format(**locals()) which is basically the same thing, but felt like a hack.
But for f-strings, it has full support for the "inline" variables.
Is this complete (meaning: it also includes more complicated expressions, referencing these variables)?
I'm asking, because some Python development tools are failing in doing it right. This leads to annoying reliability issues for refactorings (even for simple renames). Currently, I have the problem with Wing IDE.
Not sure what you mean by complicated expressions? If you mean the variable is referenced in a complicated statement with calcs or as part of a ternary operator, etc... AFAIK they are all supported. It even understands scope. So it won't go outside of the current scope where the variable is defined.
PyCharm's refactoring is very neat. In certain instances it even "refactors" relative paths inside a string if you move the file being referenced. Moving files around is intelligent and all imports get fixed. It has neat naming-convention renaming (myVariable->my_variable). Lots of nice little quality-of-life features, which is why I think it is the best Python IDE out there atm. Give it a whirl, it's got a free community edition that is near 100% functional!
https://youtrack.jetbrains.com/issue/PY-39750
https://www.jetbrains.com/help/pycharm/configuring-third-par...
That said most apps probably won’t see much of a difference in perf between the two approaches.
https://github.com/PyCQA/pylint/issues/2395
They are nice to use. But they are also the single feature that I'm most likely to use that means I can't use pypy to run my code many times faster.
I wish that pypy supported them.
However upon reading the article, it does feel more ergonomic to prepend your string literal with an 'f', and have it automagically reach out into the surrounding scope for the variables to format into the string.
I do worry a bit however about formatting not just expressions, but being able to call other code. Yes, that could be quite convenient for keeping the code concise and avoiding the kind of verbosity that confuses. However, I could also see someone forgetting to sanitize user-input data, and combined with the f-string, creating a remote arbitrary code-execution vulnerability. Sure, an attacker can't immediately start scribbling on memory in Python, there's more work he has to do first. But, he possibly could hijack the Python process to run whatever Python code he wanted.
You could of course eval something the user provided, but there's no more of a risk with f-strings than there is with regular code.
This is addressed in PEP 498.
1. \h doesn't get interpolated to anything (unlike \a, \b, \f, etc...)
2. The "r" needs to be in the original definition, not the f-string. You only need the "r" in the f-string if you don't want interpoolation of the "\" - I.e.:log.debug("Unexpected, got %r", (got,))
By doing it this way, it can defer rendering the string until it is actually needed, which is (assumed to be) a win when the logging module is normally just going to toss the low level messages away rather than emit them anywhere.
I think you lose this optimization if you use f-strings. I don't think rendering of the string can be lazy, deferred until actually needed.
So:
Or, to just use the available locals to automatically fill that in for you: Or if you want both globals and locals with a preference for locals:Also, shoutout to Template for untrusted strings. It’s surprisingly unknown. Most people suggest replace or a custom format function.
(Also, for the logging functions, you don't pass a tuple, just a normal series of arguments.)
(That's what I get for assuming consistent behavior :P)
You still miss out on the scoping of f-strings, and embedding function calls. But passing around what amounts to an "eval" expression is a footgun that should obviously be frowned upon.
There's also an option for $-style Template strings. But I can't recall when I've ever seen someone use that.
The style option only applies to the outer format applied by the formatter (timestamp, level, file, line, etc), it does not apply to the logging calls.
Limiting logging to `%`-formatting seems to be an inconsistency similar to supporting `%` but not `format` on `bytes` and `bytearray`.
the logging module predates the `.format` function.
The rules for the trailing `=` case are particularly complicated:
https://bugs.python.org/msg341732“=” in the format-spec (the thing that comes after the “:”) is the numeric padding alignment specifier (other alignment specifiers are left “<”, right “>”, and center “^”.)
You can also actually combine those syntaxes - I often use f"{foo:%Y-%m-%d!r}" to get a quoted, properly escaped date in a specific format, for example.
Ruby: Good but there are too many kinds of syntax. Use double quote to interpolate which need to escape a lot. And single quote doesn't interpolate, which makes many code bases very mixed and merging code is annoying. I wonder if they swapped them in the first place would it make the mixed thing better because it's easier to settle on single quote?
C#: Support multiline, still awkward because it uses double quote.
JavaScript: Simple, intuitive yet extensible. It's pretty useful because in JavaScript world embedded languages are very common (CSS, HTML etc). And I find ${} is slightly better than {} in C# and Scala because of the escaping. The community is pretty much settled with single quote, and only use back tick when interpolating so it suck less than Ruby.
Scala: Extensible. Very thoughtful and cool, you can indent and strip margin. It's a little bit cumbersome to write multiline when it's space sensitive in JavaScript
Elixir: Extremely extensible, the Sigil is not only for strings but also for other syntax.
Clojure: It's weird that I put Clojure here, yet I find (str "Hello" name "!") is not far from dedicated interpolation syntax. S-expr, simplicity and all that.
I would say Clojure's is good enough for me, but JavaScript is pretty awesome already - maybe it would be perfect if it can strip margin as well.
In the examples I've used `_` as an alias for a `gettext` call - I believe this is a common practice to do something like `from django.utils.translation import ugettext as _`.
This bit of additional functionality in both JS and C# makes their interpolated strings/template literals extremely useful for i18n (and other uses such as query parameterization and injection attack avoidance).
I'm surprised that in comparing the efforts of other languages Python didn't include such functionality in the f-strings PEP. I wonder if such a feature could be added in a non-disruptive way. Maybe a meta-property like a __format_parts__ or something?