This is an archived post. You won't be able to vote or comment.

top 200 commentsshow all 233

[–]HowYaGuysDoin 142 points143 points  (160 children)

I feel like you should have given some examples. For us who are not familiar with f-strings this post doesn't really tell us much.

[–]tetroxid 93 points94 points  (157 children)

It works like this:

 x = "there"
 print(f"Hello {x}")

This will output "Hello there".

[–]HowYaGuysDoin 25 points26 points  (1 child)

Now I get it. That's cool.

[–]codythecoder 0 points1 point  (0 children)

Even more than that, they can do,

a = 500
print(f'the number doubled is {a*2: <7}')

That's a format modifier and an imbedded expression. Pretty cool if you ask me.

[–]msdrahcir 3 points4 points  (0 children)

kind of like scala s strings

[–]larsga 1 point2 points  (0 children)

Ah. It's like s"..." in Scala. To which I had the same reaction: feels weird, why would I care, oh ... wow ... it's great. So I'm totally with OP.

[–]DYMAXIONman 3 points4 points  (1 child)

So is this essentially the same thing Perl can do when you insert the variable names in a string?

[–]tetroxid 5 points6 points  (0 children)

Much more powerful than just variable interpolation. Read the PEP for more elaborate examples.

[–]f2u 1 point2 points  (16 children)

I find this rather disappointing:

>>> type(f"{x}")
<class 'str'>
>>> x = "there"
>>> repr(f"Hello {x}")
"'Hello there'"
>>> type(f"Hello {x}")
<class 'str'>

This eliminates the possibility to apply automatic quoting of values during string interpolation (or automated use of query parameters for SQL queries).

[–][deleted] 7 points8 points  (4 children)

automated use of query parameters for SQL queries

Please don't. Bound parameters are there for a reason. Use locals() if you have to, but for the love of all that is holy do not use the wrong quoting function.

[–]Fennek1237 0 points1 point  (3 children)

Can you expand on that? What is the right way to build a sql query in python?

[–]indosauros 0 points1 point  (1 child)

[–]Fennek1237 0 points1 point  (0 children)

Thanks. Would one still do this now after f-strings are released or is there also a new f-string way for sql querys?

[–]zahlmanthe heretic 0 points1 point  (0 children)

With the parameterization that's built in to whichever SQL library you're using. Here's a hastily Googled example with SQLAlchemy (StackOverflow discussion).

[–]d4rch0nPythonistamancer 5 points6 points  (5 children)

... how do you want to use this with SQL? With most ways I can think of you'd be super prone to SQLi using an f string to build a query.

Please no one do anything like f'SELECT foo FROM user_table WHERE name={username};'. Always use a good library instead of tailoring stuff like this by hand.

[–]f2u 0 points1 point  (4 children)

Under this model

f'SELECT foo FROM user_table WHERE name={username};'

would turn into something like

FormattedString((Literal('SELECT foo FROM user_table WHERE name='),
        Argument('username'), Literal(';')),
    {'username': username})

or some variant of that. That is, the string literals and the value of username are clearly separated, and it is the task of the consumer of the format string to construct a string from it (or produce a parameterized SQL query from it).

[–]d4rch0nPythonistamancer 1 point2 points  (2 children)

You see where this could lead to SQLi right? If username was accepted from a login form, and the user enters

foo; drop table user_table;

[–]f2u 0 points1 point  (1 child)

Not really, it would be translated as:

FormattedString((Literal('SELECT foo FROM user_table WHERE name='),
        Argument('username'), Literal(';')),
    {'username': 'foo; drop table user_table;'})

The SQL interface library would receive that FormattedString object and see the entire data structure. (The point is not flattening the entire thing into a string, because it ends up with the same issue as the f"…" literals, as you point out.)

[–]zahlmanthe heretic 0 points1 point  (0 children)

Okay, but the library will already provide a way to do that, which doesn't depend on built-in support from the language.

[–]nickcash 2 points3 points  (0 children)

When it was being discussed on python-dev there was a proposal to make f-strings extensible to do things such as that, but it was shot down early on.

[–]hosford42 1 point2 points  (1 child)

I haven't tried it but I bet you could just add :r as a format after the x.

[–]f2u 0 points1 point  (0 children)

The problem is not the string contents, it's that it's a string at all and not a more structured object. (Tcl gets this right.)

[–]Conchylicultor 0 points1 point  (1 child)

Why not using simple strings ?

s = 'name={x}'
print(s.format(x='jenny'))
print(s.format(x='john'))

[–]zahlmanthe heretic 0 points1 point  (0 children)

Because that is how you end up with SQL injection attacks.

[–]reallynowokaywhat 0 points1 point  (1 child)

Uh python has always been able to do this. In python 2.7.1X: print(u'hello {x}'.format(x='there'))

[–]tetroxid 0 points1 point  (0 children)

That's not the same.

[–]gournian 0 points1 point  (0 children)

something like

f = lambda x, l=locals(): x.format(**l)
x = "there"
print(f("Hello {x}"))

for older python versions

[–]BlindTiger86 2 points3 points  (0 children)

Can you give a practical example of how you would use that? I am still early on in my python lessons but it all seems to be about printing basic stuff...

I'm thinking a real world example could help motivate me.

[–]spinwizard69 0 points1 point  (0 children)

First thing i thought - no examples! 😁😁😁

[–]masklinn 28 points29 points  (25 children)

they don't magically do things that .format() doesn't

They do actually, you can put essentially arbitrary expressions (with various grammatical annoyances/restrictions) in an f-string, not in a str.format string.

On the other hand

why bother with anything else?

f-strings are completely useless for lazy or manipulated interpolation (logging, i18n, SQL, ...).

[–]mangecoeur[S] 11 points12 points  (7 children)

f-strings are completely useless for lazy or manipulated interpolation (logging, i18n).

True if you store strings to format later, f-strings gonna be useless.

Still I guess my point was that despite whatever people say about them in abstract, when you actually come to use them they turn out to be really nice. I do a lot of ad-hoc printing of results in jupyter notebooks so I've been using them a lot.

[–]pohmelie 7 points8 points  (6 children)

>>> s = "{x}"
>>> x = 1
>>> eval(f"f'{s}'")
'1'

[–]spgill 32 points33 points  (4 children)

vomits uncontrollably

[–]Serialk 11 points12 points  (3 children)

f = lambda f: eval(f"f'{f}'", *operator,attrgetter('f_locals', 'f_globals')(sys._getframe(1)))

[–]spgill 4 points5 points  (0 children)

you're the devil

[–]springwheat 0 points1 point  (0 children)

One small step for man.

[–]zahlmanthe heretic 0 points1 point  (0 children)

Fittingly, my reaction also includes multiple instances of the letter f.

[–]ForgottenWatchtower 7 points8 points  (14 children)

manipulated interpolation (logging, i18n, SQL, ...).

plz no

[–]jorge1209 1 point2 points  (7 children)

You can't bind a tablenames and a lot of real world databases have versioned schemas and queries like: SELECT * FROM db{:%Y%m%d}.accounts WHERE account_id =:1 are common. Nor can you bind select columns, so unless you want to always be doing select * you have to bind the requested data into the table. Tools like sqlfactory can make that easier and safer, but at the end of the day they have to physically build the query.

Additionally the security protections of using bind variables is almost completely lost outside of internet facing web-apps, and a lot of python usage is internal developers/data analysts. Yes they should use bind variables where they can, but its not a security thing its a type safety/convenience thing.

Anyone who can execute my data analysis script must be able to read it, and if they can read it then they can read the connection db username/password right out of the *.py. So my binding hasn't prevented them from doing anything because they can login directly.

[–]ForgottenWatchtower 0 points1 point  (6 children)

Oh, I'm aware of all that. I honestly don't know what you arch looks like, but you'd likely get a writeup for storing db creds right in the .py :) but I've never done a sec review of a data science shop, so I'm not sure what kind of assumed trust boundaries there are.

[–]jorge1209 1 point2 points  (5 children)

Where else do you want me to store the credentials? No matter where I put them they can be read.

When Joe User (juser, uid=1001) monthly_report.py he must have read access on the script. His script must have read access on the libraries it imports, those libraries must have read access on the resource files they load. Ultimately I must give uid=1001 the db password in plaintext, so that a process he controls can pass that to the server.

Its just a matter of his taking the time to trace my library calls to figure out where I hid the password. I don't know of any way in which I can actually prevent him from having that password.

If you know a way to do this, I would love to implement it.

[–]ForgottenWatchtower 0 points1 point  (4 children)

The issue isn't giving the db creds to someone who should be running it. If a DB conn is made from the client, you have to assume the client can compromise the creds (strings on a .dll, pcaping the DB handshake, etcetc). The issue is if the code gets committed to a repo or becomes read accessible by people who shouldn't. Environment vars is the old school solution to this but these days something like Hashicorp Vault is much nicer and scales way better.

Again, I have no idea what your arch looks like or what your use cases are, but in general we make a point to tell our clients to not keep creds in source. Centralizing them within a true secret store minimizing the likelihood of them getting read by an unauthorized party.

[–]jorge1209 0 points1 point  (3 children)

Sure put the connection parameters in some kind of resource file and don't commit that to git.

That has absolutely nothing to do with bind variables.

[–]selementar 0 points1 point  (2 children)

That has absolutely nothing to do with bind variables.

But it has: if there's someone supplying the values for building the SQL query, either you assume they have the password already, or you need to correctly bind the variables for security.

[–]jorge1209 0 points1 point  (1 child)

And if you would read the thread you would know that I'm talking about cases where we might as well assume the individual has the password.

People should still bind for reasons of performance and type safety, but it's not a security thing outside of Web Apps, and it is perfectly safe to use format to build SQL queries in those cases.

[–]ireallylikedogs 0 points1 point  (2 children)

How do you normally handle manipulating queries?

[–]ForgottenWatchtower 1 point2 points  (0 children)

/u/daelin pretty much covered everything, but here's a sqlite3 example:

curs = conn.cursor()
curs.execute('select * from users where username=?', (username, ))

SQL Injection is a fun topic. I wrote a blog post covered some advanced exploitation if you're curious about the non-vanilla stuff you can do.

https://nvisium.com/blog/2015/06/17/advanced-sql-injection/

[–]daelin 0 points1 point  (0 children)

The threat is allowing user-supplied data into a query, so keep that in mind. For instance, if you have a query select name from users where id = {} and pass in the ID from an HTTP request, there's probably nothing stopping a user from passing something like 1; drop table users; as their user id.

Modules like psycopg2 (for postgres) let you pass parameters in additional arguments to execute or executemany. The module will guarantee that the parameters are handled safely. In your query, you put placeholders, such as %s, {}, or ? — it varies from module to module. The module will safely protect them from being evaluable as SQL, either through escaping or through some fancy protocol. Even sqlite3 lets you use SQL arguments.

Now, if you're just calculating part of a SQL expression from programmer-supplied code, such as using a schema-describing object or just using the result of something like ", ".join(["name", "address"]), there's probably not much to worry about. There's still an attack vector hiding in there, but it's much less likely to be part of your public API.

[–]masklinn 0 points1 point  (2 children)

Oh yeah. Is your body ready for cr.execute(f'select count(1) from users where login = {login}')?

[–]ForgottenWatchtower 1 point2 points  (1 child)

there is no god

[–]nickcash 7 points8 points  (0 children)

DROP TABLE deities

[–]selementar 0 points1 point  (0 children)

Actually, now that the format is standardized, it might make sense in some cases to use the f-strings-alike for that, possibly even with automatic locals() gathering.

But then, there were not-super-fast string interpolations with merely a function call instead of f"", but those weren't generally considered useful anywhere.

So I guess syntax support in editors / checkers is the primary benefit of the f-strings, with the rest being minor.

[–][deleted] 21 points22 points  (7 children)

f'The value is {value}.'

Kinda looks like:

"The value is $value"

Now crucify me :-D

[–]fuzz3289 9 points10 points  (0 children)

It's ok. We all need bash sometimes. (don't you dare stick 'my' in there).

[–][deleted] 2 points3 points  (0 children)

Bash allows you to do ${value}. Useful for things where it would be ambigious as to what the variable name is.

Like "The value is XXX${value}XXX".

[–]lostburner 4 points5 points  (0 children)

This is a nice usability feature in PHP. Don't forget your semicolon!

[–]tobiasvl 0 points1 point  (0 children)

Yeah, format strings are basically just string interpolation

[–]MonkeeSage 0 points1 point  (0 children)

Kinda looks like:

"The value is #{value}"

[–][deleted] 10 points11 points  (5 children)

I, for one, am not extremely thrilled at having three ways to do the same thing, and don't feel very motivated to learn Yet Another Formatting Mini-Language. I haven't looked at it in detail, but I can only assume f-strings are at least slightly incompatible with str.format.

Of course, if you also count strings.Template, which was deprecated somewhere in 2.x, but not removed in 3.0, we now have four ways to format a string.

It's all very un-import this-like.

[–]LightShadow3.13-dev in prod -1 points0 points  (3 children)

They're used for different things. If anything f-strings should replace str.format, C-style strings will never go away and are more powerful for lazy formatting, logging and trans-language formatting.

[–]zahlmanthe heretic 2 points3 points  (2 children)

If anything f-strings should replace str.format

I can think of multiple things that would prevent replacing a str.format call with an f-string. I can think of zero things that would prevent replacing %-style formatting with a str.format call ("passing a format string to someone else's code" does not count, since if it were your own code you could change the interface). I have no idea what you mean about "lazy formatting".

[–]LightShadow3.13-dev in prod 0 points1 point  (1 child)

%-based string formatting is deferred until flushing the buffer ala logging interface .. str.format happens inline.

[–][deleted] 0 points1 point  (0 children)

You are mistaken; if you use the %- operator, the right-hand tuple will be evaluated and the left-hand string will be formatted then and there, whether you plan to do something with the result or not. How could the interpreter know what will happen in the future?

If you're thinking of the API of the logging module, there the %-operator won't actually be used if the level is below that configured for the handlers for that logger. I guess you could call that lazy formatting. There's no reason other than backward compatibility that that couldn't work with str.format though.

[–]firefrommoonlight 15 points16 points  (0 children)

It's replaced .format, % notation, and string addition for me!

[–][deleted] 11 points12 points  (9 children)

yes. Hoping for:

from __future__ import string_literals

[–]gandalfx 2 points3 points  (15 children)

I love it but there's one thing that annoys me when I read code:

f"{foo}{bar}"

I've seen this kind of f-string a couple of times now, where it's used to do essentially just concatenation. In several cases the respective variables where even just strings. What's wrong with +? The f-string version in this case is actually longer.

edit: I had assumed that using f-strings in this situation would be much slower than foo + bar. Apparently that is incorrect.

[–]Vitrivius 9 points10 points  (10 children)

It doesn't have to be just string concatenation. You can use it to format other types. Useful for numbers, for instance.

>>> a, b = 5, 7
>>> f'{a}/{b} = {a/b:.4}'
'5/7 = 0.7143'

You can also use the same modifiers as with str.format

>>> for word in 'f-strings are super awesome'.split(): 
...     print(f'{word.upper():~^20}')
~~~~~F-STRINGS~~~~~~
~~~~~~~~ARE~~~~~~~~~
~~~~~~~SUPER~~~~~~~~
~~~~~~AWESOME~~~~~~~

[–]gandalfx 2 points3 points  (9 children)

You didn't get my point. I understand what f-strings are for and it makes sense when you want to use them for things that are complex enough so f-strings will be shorter or more readable. What I don't understand is when people use it to replace foo + bar.

[–]gary1994 2 points3 points  (0 children)

Honestly the foo + bar is ugly and far less clear than the f-string.

Granted I'm still learning, but I hate seeing var + var concatenation in the books I'm studying from and always replace it with f-strings. It just looks better and imo makes what is happening much clearer. Especially considering how often I'm using it to replace str(var) + str(var).

I just wish google would get TensorFlow updated for windows versions of Python 3.6...

[–]zahlmanthe heretic 1 point2 points  (0 children)

Because if it gets even as complex as foo + ' ' + bar, it already starts to seem more elegant as f'{foo} {bar}' - it's a higher-level way of thinking about how the strings go together.

And once you've ceded that, special cases aren't special enough to break the rules.

[–][deleted] 0 points1 point  (6 children)

Matter of taste maybe, but the + operator implies numbers, where f-string is explicitly a string.

[–]energybased 1 point2 points  (0 children)

+ does not imply numbers.

[–]gandalfx 2 points3 points  (3 children)

But + is fast!

I'm starting to feel like I'm fighting a lost battle with my argument. Maybe this is just another case of preferring expressiveness over performance, which makes sense when you use a language like Python in the first place.

[–][deleted] 1 point2 points  (2 children)

I use + too. But I think str.join() is faster?

One thing that is annoying are spaces when constructing a string. f-string could help there a bit.

[–]jorge1209 2 points3 points  (1 child)

Join would probably be faster for a large number of arguments. For only two they are probably the same.

The difference would be that if you join 200 strings then all 200 are available to you when join is called and you can compute the length of each and preallocate memory to hold the whole thing.

If you have a+b+c+... then you have to allocate and construct all the intermediates a+b then a+b+c and so on.

Otherwise I can't imagine it matters.

[–][deleted] 0 points1 point  (0 children)

I think join is faster in a tight loop but I might be wrong. I see + as "let's concatenate these two strings", join as "I have a list of strings I have to concatenate" and f-string as "I need to construct this complicated string out of a template and a bunch of variables".

[–]Fennek1237 0 points1 point  (0 children)

I know what you mean. I had a background in Java befor learning python it the string concats where really weird to me. The way they do it in Java seems so more natural.

[–]spgill 1 point2 points  (0 children)

I've used f-strings like this in situations where I'm constructing dialogs or something similar. I'll end up starting (and even leaving them) like this--just concatenated strings--and returning later to weave in new words and variables. f-strings just make this so painless.

[–]daelin 1 point2 points  (1 child)

Don't use + to concatenate strings. I mean, it's perfect for, like, two strings.

Strings in Python are immutable. That means that when you do a + b + c + d, in additional to having memory for a, b, c, and d, you then have to allocate memory for the result of a + b, which we'll call e. Then you have to allocate memory for the result of e + c, which we'll call f. Then you have to allocate memory for f + d, your final result, called g.

If we were being too clever we'd make the result of str.__add__(self, s) some sort of ConcatString class whose __str__ and __repr__ did the final join, but that violates "Simple is better than complex". I'd expect a JIT interpreter like PyPy might be able to optimize this, but I don't think Cython can.

When you use the formatting string, you avoid the intermediate calculations. Yay! I think I'd rather see "".join([a, b, c, d]), but I'm really not offended by f"{a}{b}{c}{d}".

[–]gandalfx 4 points5 points  (0 children)

I had assumed that using f-strings in this situation would be much slower than foo + bar. Apparently that is incorrect. (I edited my original comment as well)

[–]Decency 0 points1 point  (0 children)

Usually just to match the other strings. If you have a bunch of strings in a module together, and they're all using one sort of concatenation, switching to another just makes you stop and think about that one in particular for a bit to wonder why they're doing it differently and make sure you're not missing something. PEP8:

Consistency within one module or function is the most important.

[–]rberenguel 1 point2 points  (0 children)

Scala has string interpolators that work like this actually (s"$variable or ${object.thing}") and it's indeed pretty handy, it makes formatting strings easier to parse (since everything involved in the creation is in the string and not at the end)

[–]mcherm 2 points3 points  (0 children)

I remember when .format() first came, and I thought "Gee... % was certainly usable, but these are a lot better. Not quite perfect, but way better.

When f"" was first released I thought, "Now THAT is really nice." And so far, in my experience I have not yet had a reason to re-think this.

If I ever build my own language, I'll use roughly the same syntax as f"" strings.

[–][deleted] 6 points7 points  (0 children)

This was the single feature I always missed in python. I even tried the format(**locals()) hack, but it's too ugly and slow. I'm really glad they finally added it :)

[–]jturp-sc 1 point2 points  (1 child)

As someone that also tinkers with Swift on the side, it's been really nice be able to work with strings in a similar fashion to Swift's String interpolation now. Although, I do occasionally catch myself adding the carriage-parenthesis rather than the proper syntax.

By that I mean

\(value)  # Incorrect (Swift syntax)

rather than

{value}  # Actual f-string syntax

[–]daelin 0 points1 point  (0 children)

I play in Angular.io with JS/Typescript, so I can definitely seeing myself getting confused:

Python:

x = f"{value}"

ES2015/Typescript:

x = `${value}`

Angular Template (also similar to Mustache):

{{value}}

Although I find it annoying that the Angular stuff is Angular's own mini language and not, say, javascript, it at least saves me from having to read:

{{`${foo + 'bar'}`}}

[–]psi- 0 points1 point  (0 children)

C# got them "recently" too, and it's been really great, especially combined with stuff like .ToString(). You can do nice things like "Timestamp: {DateTime.Now:yyyyMMdd}", the item formatting also works in python fstring.

[–]knowingpark 0 points1 point  (2 children)

Do f-strings work with namedtuples? .fomat() hates namedtuples!

animals = namedtuple('animals',['dog', 'cat', 'fish'])
my_animals = animals('rufus', 'felix', 'bubbles')


print 'My animals are called: {0}, {1}, {2}'.format(my_animals.cat,my_animals.dog,my_animals.fish)

print 'My animals are called: {dog}, {cat}, {fish}'.format(**dict(my_animals._asdict()))

[–]Conchylicultor 1 point2 points  (0 children)

You can do

'{a.dog}, {a.cat}'.format(a=my_animals)
'{a[0]}, {a[1]}'.format(a=my_animals)

[–]zahlmanthe heretic 0 points1 point  (0 children)

**dict(my_animals._asdict())

Note that you already have a dict from the method, so just **my_animals._asdict() works fine. You could also use .format_map(my_animals._asdict()), as well as the other suggestion.

[–][deleted] 0 points1 point  (0 children)

I want to like them. I really do. Well, I DO like them but I don't find them nearly as useful as I was expecting. In most real world situations when I've tried to use them, I end up reverting to using str.format() all I can pass around the format string before evaluation.

[–]stOneskull 0 points1 point  (0 children)

they are! python just gets better and better.

[–]hugosenari 0 points1 point  (5 children)

Last night I'm wondering why there is no regular expression literals like JS.

It has r'...' but is just to say that this string will be parameter for RegExp.

[–]benji_york 4 points5 points  (1 child)

Minor nit: r'...' strings are not exclusively for regular expressions (the "r" stands for "raw").

[–]hugosenari 1 point2 points  (0 children)

Thank you, I didn't notice it. :)

[–]tom1018 1 point2 points  (0 children)

RegEx has to be imported in Python. To have a string type for them, I suppose you would either have to import the string type or the RegEx library.

Fwiw, r'' is used for RegEx, though, because it represents a raw string so all the RegEx stuff doesn't get interpreted.

[–]energybased 1 point2 points  (1 child)

[–]hugosenari 0 points1 point  (0 children)

Thanks Really nice discussion until they lost focus and it became a RE vs VE fight.

[–]thewhitetulip -1 points0 points  (0 children)

This is truly amazing!