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.
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!
> 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.'
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 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?
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.
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.
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.
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 do love the Python F-string syntax, however, it shows a weakness of Python as it has been maturing over the years. The original Zen of Python was (PEP 20)
There should be one-- and preferably only one --obvious way to do it.
There are many ways to format a string, and F-Strings are yet another way. String formatting is not the only area where the original ZEN no longer applies. Of course this is part of a language natural evolution, however, it does make the language more confusing for new starters. Syntax sugar coating Python may also distract from solving more systematic problems, like the lack of proper multithreading in Python.
Casual programmers, especially those learning Python as a first programming language, aren't well-placed to know which language features they need. If they're learning from multiple sources and tutorial series A teaches Python 2-style %-formatting, B teaches str.format, and C teaches f-strings, then the novice programmer will learn all of these equivalent features.
This also shows up when the novice programmer wants to understand other people's code. If there is only one way to do things, then all common examples of a task should use that one way. If there are multiple ways (as here), then common examples (or an open source project of interest) may use any combination of them. To understand the code, the novice programmer needs to understand all of the right ways to do something rather than one of them.
> If they're learning from multiple sources and tutorial series A teaches Python 2-style %-formatting, B teaches str.format, and C teaches f-strings, then the novice programmer will learn all of these equivalent features.
Isn't the key though, that they aren't in fact equivalent features at all? %-formatting doesn't allow for mapping names or changing the order of variables. f-strings are just a shorter version of .format() and there's no conceptual difference in usage.
> If there is only one way to do things, then all common examples of a task should use that one way. If there are multiple ways (as here), then common examples (or an open source project of interest) may use any combination of them.
Unfortunately, despite all noble efforts of avoiding this, Python is already full of these. This is true for any sufficiently powerful language. That's why there's idiomatic ways (here: "the Pythonic way") of doing things and "freestyling":
x = []
for i in range(4):
if i % 2 == 0:
x.append(i)
y = []
for i in range(0, 4, 2): y.append(i)
z = [i for i in range(4) if i % 2 == 0]
r = [i for i in range(0, 4, 2)]
s = []
i = 0
while i < 4: # or: while (i < 4):
s.append(i)
i += 2 # or: i = i + 2
t = []
i = 0
while True:
t.append(i)
i += 2
if i >= 4: break
# x, y, z, r, s, and t will contain the same values
Every one of the snippets above showcases a different
way of doing the exact same thing in Python. Some are
idiomatic, others ugly, some are outright bad code, yet all are perfectly valid Python.
How would a novice programmer know which of the above is "good" code and what is the preferred way to do it?
A Python tutorial aimed at kids [1] never even mentions "range()", list-comprehensions, "+=", etc. yet enables children to write games in Python.
A Python intro for non-programmers [2] on the other hand shows both for- and while-loops as well as "range()".
So I'd argue that this is a non-starter as even the very basics as taught by recommended sites from the Python website itself [3] differ greatly in scope.
So I [edit]don't[/edit] find f-strings to be a big deal, especially since string formatting is a rather advanced topic anyway and beginners would stick with
for i in [1, 2, 3, 4, 5, 6, 7]:
print("The number", i, "is", "even" if i % 0 == 0 else "odd")
anyway instead of messing with f-string, %, or "format()" - whoops! - accidentally introduced yet another way of doing it :D
I think this is actually improving the Zen of Python. Before my coworkers were pretty split between % and .format(). Now everyone uses fstrings. So while there is one more way to format strings now it seems that everyone has agreed that this is the "one correct way" to do it.
> There should be one-- and preferably only one --obvious way to do it.
Yeah, and then - as the language matures - so do its users.
It's a common dilemma that cannot be fixed. You have the early adopters who grew up with the language and at some point got stuck in their ways (e.g. Python 2.x).
Replacing day-one features with new ones feels like using a new language to those who used the language for decades. The more mature (read: older and more widespread) a programming language becomes, the harder it is to introduce breaking changes.
String interpolation is one such area and due to the nature of the language such an important and integral part that it's becomes almost impossible to change.
As someone who never touched Python until very recently, I never even considered using anything but f-string formatting; I acknowledge that other (legacy) options exist, but outside of reading other people's code, I simply ignore them.
To new users who started with Python 3.x the obvious way to do it is f-strings and only language veterans switching to 3.x are affected by that feeling.
IMHO nothing changed. 2.x uses print without parentheses and the %-operator. 3.x requires parenthesis and prefers f-strings.
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?
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
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.
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...
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.
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.
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):
> 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.
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.
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 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 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 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?
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 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.
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 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.
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 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.
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.
What if one is printing the output of a function? Then one is left with reverting to positional formats. Mixing positional and named formats is less than robust. One of the common cases for function output is using pprint. As someone who has been using Python since 2.4 print formating using pprint is robust. Over the years I've found it a best practice to only use positional formatting so as to have the format string be stable when switching between usuing a variable or pprint of the variable, especially when debugging. So, I am not using f strings. Another thing is that as a decades long PERL developer I observe Python is becoming the new PERL in having fifty ways to do something. All of these ways will have small-to-large performance differences. Because of this then soon we will all have the Python Cookbook on our shelves to document them so one knows when to part ways with less optimal usages. It is always faster to print an unformatted list then a format string and in fact pylint will ding your code if logger uses a format string and not a list of strings and variables in print order. Then one begins to wonder if this is the case for logger then why not for every print statement? And thus Python is quickly degenerating into PERL code readable territory with each new version.
is that in the first case, internally it checks if the logger is disabled and if so skips formatting the string, whereas in the second case you've already formatted it before passing it in, so that work can't be avoided. This isn't the case for print statements because they don't execute conditionally, so you don't ever skip doing the format.
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).
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.
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.
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.
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.
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)
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.
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 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.
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).
https://docs.python.org/3/whatsnew/3.8.html#f-strings-suppor...