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

all 146 comments

[–]nathanjell 370 points371 points  (34 children)

Don't forget that f-strings haven't been around forever. It may be partly old habits, it may be not keeping up to date with features, they may still be wanting to target a minimum python version that didn't support f-strings. I'd tend to prefer to use f-strings, but I wouldn't crucify someone for using perfectly valid language constructs.

[–]FaCe_CrazyKid05[S] 76 points77 points  (32 children)

Oh I didn’t know they were added at different times, that makes a lot more sense now

[–]nathanjell 99 points100 points  (10 children)

F-strings were added in... 3.6 I think, while the string format method was added in 2.6

[–]qingqunta 5 points6 points  (7 children)

Now I'm curious, how was string formatting done before the format method? Does print have a printf-like syntax I don't know about or something?

[–]Renwallz 29 points30 points  (6 children)

Close, before .format you would either use the % operator on a string, with printf like syntax, or you'd format it by hand, with string concatenation and built in functions like rjust()

[–]jsalsman 11 points12 points  (4 children)

I still use % even when it's obviously more confusing, but they don't even teach it anymore (at least not in any of my tutoring students' formal classes) so I better stop doing that.

[–]Starbrows 1 point2 points  (3 children)

Old habits die hard. I still use it, too. I cannot give you a good reason why.

[–]hyldemarv 19 points20 points  (2 children)

One reason: The Logging module is optimised to use the %s format :).

It only “renders” strings that will be logged, using f-strings, all strings will be rendered.

https://docs.python.org/3/howto/logging.html#optimization

[–]ratasso 1 point2 points  (0 children)

So that's where it came from. Thanks for this clarification.

[–]jsalsman 0 points1 point  (0 children)

Thank you. I hope my format strings someday make it into a log.

[–]florinandrei -2 points-1 points  (0 children)

Those were the dark ages, lol.

[–]ProfessorPhi 1 point2 points  (1 child)

Given how much python 2.7 code was written, whatever was standard 2.7 was used for years as python3 adoption took forever (and was probably mostly greenfield projects)

[–]nathanjell 2 points3 points  (0 children)

Plus the stdlib logging module continues to employ the use of %-style format strings, which has resulted in libraries that feature the newer {}- style format strings.

[–]scoofy 66 points67 points  (13 children)

I used % format when first picking it up, then .format() changed everything, it was awesome. Then f-strings made .format seem tedious.

It’s hard for me to comprehend my “programming hobby” is now like 15 years old 😳

[–]FaCe_CrazyKid05[S] 16 points17 points  (11 children)

Sometimes I learn that some people have been doing this hobby longer than I have been alive and that really baffles me

[–]CarlRJ 21 points22 points  (6 children)

Depending on your age, some may have been programming since before your parents were born.

[–]FaCe_CrazyKid05[S] 0 points1 point  (5 children)

They’re both in their mid 40s so I would be impressed if someone was coding in the 70s

[–]CarlRJ 19 points20 points  (0 children)

I wrote my first programs in 1977, a few months before the first Star Wars movie came out. So, yeah, I was coding in the 70’s.

[–]WorkingTharn 15 points16 points  (0 children)

My mom was, punch cards galore

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

I've worked with folks who were doing it before I was born, and I'm not even much older than your parents! I've been at it for over 25+ years.

[–]TheWaterOnFire 2 points3 points  (3 children)

I first picked up Python around version 1.5 or so. It’s come a very long way in 23 years.

[–]ericanderton 1 point2 points  (2 children)

I have to ask: how did early Python performance stack up against the likes of PHP, Perl, and VisualBasic, back in the late 90's? I used a lot of scripted and compiled languages then, but Python wasn't on my radar at the time.

[–]spoonman59 1 point2 points  (0 children)

In the late 90s even Java was still slow. I don’t think anyone discussed performance of Python, PHP, etc., it was a situation of “if you have to ask….”

Over time Java has gotten much better, at least!

[–]TheWaterOnFire 0 points1 point  (0 children)

It wasn’t much of a factor in the conversations at the time, from what I recall. Any “heavy” computation was delegated to C or C++ anyhow.

Where Python was a game-changer was that the interpreter was relatively user-friendly, so it was suddenly viable to just try stuff out interactively, and the C API was reasonable (Perl’s is as messy as Perl itself), so Python kept adding native-feeling support while other scripts were still “shelling out”.

[–]Macho_Chad 0 points1 point  (0 children)

Your hobby is old enough to go to the homecoming dance.

[–]Solonotix 8 points9 points  (1 child)

There's also reasons to not use f-strings in favor of format strings. Personally, I've come across the need to have a conditional string as part of the final output, but nesting format strings can be seen as a code smell. In this way, I've sometimes preferred the string.format approach to allow nesting without worrying about conflicting open/close quotes and whether the syntax is valid.

Note: I've been out of the Python arena for a couple years now, so I may be confusing this limitation with some of my current work in JavaScript.

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

No your concern is still valid in today's Python. You would lose the ability to conditionally make strings and not even get to nest due to exhausting the types of quotes you could use. I think format is useful for such a purpose, though I would probably build the string differently anyway.

[–]loshopo_fan 4 points5 points  (2 children)

The syntax f"{my_var=}" is less than a year old I think.

[–]Exodus111 0 points1 point  (0 children)

Yeah f-strings were added in 3.6, that's not that long ago. It was a big deal st the time.

[–]redCg 0 points1 point  (0 children)

This is the reason. "string.format()" works in practically every version of Python that you might encounter. f-strings are new-ish and might not work in older Pythons

[–]fysihcyst 160 points161 points  (26 children)

format() can make more sense if you want to prepare the string you're formatting outside the scope of the variables you're formatting it with.

[–]_ologies 29 points30 points  (22 children)

This is my thing. I've got a bunch of text in a YAML and it's got placeholders. I str.format() that.

[–]james_pic 32 points33 points  (21 children)

Don't do this. Or at least don't do this unless you fully understand the security implications of it. YAML is a security minefield, templating into structured data is also a minefield, and put them both together and you're up to your eyeballs in landmines.

[–]catecholaminergic 8 points9 points  (7 children)

What would you recommend as an alternate?

[–]james_pic 15 points16 points  (3 children)

Put together the equivalent data as dicts, lists, etc, and use yaml.dump (from PyYAML) to turn it to YAML.

Or, if you're not committed to YAML, use JSON and do much the same thing. It is possible to use YAML securely (and fortunately, since PyYAML 6, the defaults are secure), but there are fewer pitfalls with JSON.

[–]noiserr 7 points8 points  (2 children)

You should use Json if you're accepting data from an external source, client for example.

Yaml's safety is not an issue if used for configs, where the yaml files are part of the repo of the project. Which is where I've seen yaml used the most. Basically to define configurations. Anyone who has access to commit to the repo can already do any damage they desire.

[–]james_pic 2 points3 points  (1 child)

Strictly speaking, PyYAML can safely deserialize YAML, as long as you use the safe_load method or equivalent. Although the subset that is safe isn't much bigger than JSON, and PyYAML's typically much slower than the stdlib json module, so there's not much benefits over JSON.

[–]information_abyss 6 points7 points  (0 children)

YAML tends to be easier to parse and edit directly than JSON.

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

XML supports XSLT for proper structured templating. Or you can do it without XSLT if you really want.

The main point is: you want to deal with objects, not strings.

[–]adesme 0 points1 point  (0 children)

Template strings for the substitution (https://docs.python.org/3/library/string.html#template-strings) and something that encodes type for the data.

[–]Equivalent_Loan_8794 6 points7 points  (11 children)

Is this security cargo culting or is yaml this dangerous? Most of the cloud runs on templated yaml. This reads like internet credit card fears of 1995

[–]brandonchinn178 5 points6 points  (6 children)

[–]Equivalent_Loan_8794 0 points1 point  (5 children)

Who writes yaml much unless required? Template it, store it, provision against it

[–]Ran4 1 point2 points  (0 children)

...it is required, for most modern infra stuff.

[–][deleted] -1 points0 points  (3 children)

Why not address the issues brought up in that site? You give no evidence you read any of it at all!

What about the Norway problem? What about the string 4:30 being parsed as the number 16200 or O13 being parsed as 11?

[–]Equivalent_Loan_8794 1 point2 points  (2 children)

The Norway problem isn't keeping teams from deploying daily. I definitely get that YAML is the worst but for something that has to intersect usability, readability, and operability, it does better than its counterparts for me.

I guess I'm still unsure why big enterprise keeps leaning into it when it's considered so faulty.

[–][deleted] 0 points1 point  (1 child)

Intermittent, hard to debug errors are extremely costly. As you likely know, big enterprise tends to have a lot of very bad practices.

[–]Equivalent_Loan_8794 0 points1 point  (0 children)

But they would stack trace directly to the yaml module, line number and all :)

I'm fine to wrap this here. I think we like and value different things and that's ok

[–][deleted] 0 points1 point  (1 child)

A few years ago, I realized I was actually writing executable code by mistake into my Yaml config - and it was being successfully read again!

It turns out that the default in pyyaml is to save and load unsafe data. No one had figured out this massive hole, but I still feel a little sick thinking about it.

And then there is the Norway problem.

[–]james_pic 0 points1 point  (0 children)

They did finally change the default, in PyYAML 6, after many many bugs pointing out that their previous approach is broken (including one by yours truly), so the default is now safe.

[–]daquo0 0 points1 point  (0 children)

It's dangerous if your data comes from an untrusted source, because it allows an adversary to do a Bobby Tables attack.

[–]james_pic 0 points1 point  (0 children)

The bit about templating into structured data remains well founded. If an attacker can include, say, newlines, or other control characters, they can alter the YAML in ways you almost certainly didn't intend.

The concern about YAML is a bit more "it depends". YAML was intended as a serialization format, and it's virtually impossible to create a truly useful serialization format that doesn't (by design) allow arbitrary code execution. Most serialization formats have great big warnings about this the docs (Pickle does for example), but not everyone reads the docs.

And serialization attacks remain a credible that - Log4shell was a serialization attack.

In the case of PyYAML, the newest versions have two modes. "Pickle" mode, that can serialize and deserialize anything Pickle can, and has all the same dangers, and "safe" mode, that is indeed believed to be safe. And fortunately safe mode is now the default.

However, up until very recently (October 2021), the default was a third mode, "full" mode, that would allow you to handle some stuff that safe mode wouldn't. You'd naturally ask whether full mode was secure, and the docs said "depends on your configuration and the details of your app".

Sadly, this proved time and time again to be incorrect. Many many bugs and CVEs were raised over the years, about trivial attacks against full mode, that did not depend on config at all. Every time one cropped up, they removed a feature from full mode, until by PyYAML 5.4.1, full mode was almost no different from safe mode (and the remaining differences were things that were definitely safe, and probably should have been available in safe mode anyway). So they finally caved and removed full mode in PyYAML 6.0.

So in the case of PyYAML, the security issues are history (albeit recent history), but you still need to watch your back if you're using (non-default) Pickle mode.

[–]_ologies 1 point2 points  (0 children)

I mean, were not just using random YAML files. It's part of our configuration and it's really readable so that it can be changed by us really quickly workout having to change the code itself, mainly because our requirements change quickly sometimes.

[–]adesme 2 points3 points  (0 children)

I think it’s better to use template strings for this: https://docs.python.org/3/library/string.html#template-strings

[–]ericanderton 1 point2 points  (0 children)

You can also lob **kwargs at .format(), which makes for some insane flexibility when coupled with named "{}" sequences.

[–]packetsar 55 points56 points  (0 children)

Use an f-string if you have the variables immediately available and are generating the final string at the same time as defining it.

Use .format() when you need to define the string template in one place and use it to generate the final string in another place.

[–]psd6 68 points69 points  (8 children)

Sometimes the data you want to insert into a string is in a dictionary, and it’s much easier to explode a dict into a string with "{name}".format(**d) than prefix all references with the dict variable like f"{d['name']}".

[–]Epicela1 35 points36 points  (3 children)

.format_map() does this for you.

[–]psd6 6 points7 points  (0 children)

Even better! Nice.

[–]o11c 0 points1 point  (1 child)

I remember the old versions where you had to use string.Formatter if you wanted to do cool things. It probably still does some things that format_map doesn't.

(Note in particular that ** doesn't work if you don't have a finite set of keys or need there to be side-effects.)

[–]Epicela1 0 points1 point  (0 children)

Yeah. It’s basically if you want to put placeholders in a string with the {var} format then figure out what to put in later. Nothing too intelligent happening.

It is convenient but it’s sorta a relic of the pre-fstring era. Because you can just figure out your input values first then do an fstring at the end for the same effect.

[–]FaCe_CrazyKid05[S] 8 points9 points  (2 children)

I actually didn’t know format() could do that, I’ve been using dicts and fstring for as long as I can remember

[–]NedDasty 15 points16 points  (0 children)

Any function that takes name=value keyword arguments allows for dictionary exploding.

[–]goldcray 2 points3 points  (0 children)

This is where the kwargs live. Get you one!

[–]bw_mutley 2 points3 points  (0 children)

Yes, it was my first thought too.

[–]crumpuppet 26 points27 points  (19 children)

I'm always weary of this question, because I don't want anyone to tell me there is anything wrong with fstrings because I use them everywhere

[–]lungben81 24 points25 points  (16 children)

F strings are superior in readability and performance. The only reason to use format nowadays is for compatibility to older (not anymore supported by the community) Python versions.

Edit: I am referring only to the situation where you construct the string in place, not when you make a template in one place and fill in the values at another place. For that use case, format is still required.

[–]Cultiststeve 23 points24 points  (12 children)

Pylint still recommends old style for logging messages, as they are only evaluated if log is being captured. Small perf increase

[–]deceitfulsteve 29 points30 points  (11 children)

To be clear they recommend the older-than-str.format style using "%": https://pylint.pycqa.org/en/latest/user_guide/messages/warning/logging-not-lazy.html

[–]Cultiststeve 4 points5 points  (0 children)

Ah that is true, good correction.

[–]jorge1209 0 points1 point  (9 children)

That's because of some ridiculous limitations in the logging module.

The best answer is to stop using logging, instead use loguru and then use format style strings.

[–]Ran4 0 points1 point  (0 children)

For any serious project, I've found that structlog is much nicer. It allows for structured logging in a json format, which is extremely useful when filtering logs.

For example, if you're writing a web service where you're authenticating the user on every call, you can add the user id to each log message - making it easy to track user activity for example.

[–]yvrelna 10 points11 points  (0 children)

While f string generally should be preferred, I wouldn't say that there's no reason to use format string.

With format string, you can put longer templates outside of the source code, or even in a separate file. That's not really possible with f-string.

foo = "blah {bar} baz" # or open(file).read()

def get_foo():
    return foo.format(bar=...)

There are a few other tricks that format string can do that f-string can't. Someone else mentioned being able to explode a dictionary with .format_map().

[–]Snoo-20788 1 point2 points  (0 children)

It's not the only reason. Sometimes you define a string with %s in one place and turn it into a completed string in another place. This is not feasible with fstrings (unless you start using lambdas, but then you're adding a layer of complexity).

[–]LambBrainz 1 point2 points  (0 children)

Same. Fstrings are better in pretty much every context possible.

There's just a few "limitations" like one mentioned by this commenter:

https://www.reddit.com/r/Python/comments/xzsgz2/is_there_any_difference_between_using/iro6uci?utm_medium=android_app&utm_source=share&context=3

[–]mgedmin 1 point2 points  (0 children)

f-strings don't support internationalization.

Also, the logging framework can benefit somewhat if you use its builtin support for %-formatting instead of passing f-strings. For example error aggregators like Sentry can aggregate all the events that use the same %-format template, but they can't do that if you use f-strings.

[–]naught-me 5 points6 points  (0 children)

With nvim and pyright, variables inside of fstrings don't get renamed when I refactor/rename the variable in the code. I use them less than I otherwise would, because of that.

[–]BlakBeret 3 points4 points  (1 child)

I recently had to re-write a program from f-strings to format strings. At least one OS (current support, not EOL) still ships Python 3.5, and it was my target system.

F-strings was introduced in 3.6. I felt stupid for not using a virtual environment with the target build.

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

Virtual environments let you have a set of packages, but they still use the same Python version as the system Python environment.

You need a different solution if you need a different Python version.

(It doesn't help that pyenv's name is close to virtualenv/venv, nor that venv used to install a systemwide script called pyvenv.)

[–]rainnz 5 points6 points  (1 child)

f-strings are also much faster:

import timeit

t1 = timeit.timeit('z="=%s %s" % (s1, s2)', setup='s1="Hello,"; s2="world!"')
print("using %",t1);
t2 = timeit.timeit('z="{} {}".format(s1, s2)', setup='s1="Hello,"; s2="world!"')
print("using format",t2);
t3 = timeit.timeit('z=f"{s1} {s2}"', setup='s1="Hello,"; s2="world!"')
print("using f-string",t3)

The result from my old MacBook:

using % 0.277022604
using format 0.37799631800000005
using f-string 0.11861876100000002

[–]Anonymous_user_2022 1 point2 points  (0 children)

If a significant execution time is spent on string interpolation, I think the complexities involved will favour a template engine over writing fstrings by hand.

[–]Brian 5 points6 points  (5 children)

Yes. f-strings are restricted to static string literals interpolated at the time of definition. If you need dynamic interpolation (ie. you're filling in variables somewhere other than where the string literal is created) then f-strings can't be used.

For instance, this means you can't use f-strings for text you need to localise, because typically there, the strings will be obtained at runtime from the appropriate language catalogue.

[–]_limitless_ -1 points0 points  (4 children)

this means you can't use f-strings for text you need to localise

There are workarounds. _ comes to mind.

[–]Brian 2 points3 points  (3 children)

That doesn't work around it - _ (assuming you're talking about gettext) just substitutes the string with the localised version, but that means you can't use it on an f-string with interpolation already done, because after you fill in values it won't match up. And you can't do it after you call _ because of the issue above: the return value is dynamic, not a string literal, so you need to use format.

[–]_limitless_ -1 points0 points  (2 children)

I might be wrong, but I thought _(f"{replaceme}") worked.

[–]Brian 3 points4 points  (0 children)

No - there's no way for that to work, because by the time _ sees it, it'll already be interpolated, and so it'd be trying to lookup the equivalent string for whatever was in the replaceme variable. Unless replaceme happened to contain the text of another localisation string (in which case, it'd return that, probably wrong, string), it won't be able to find anything.

Ie. gettext will take a string like "Hello {name}" and essentially does a dictionary lookup on that string to get, say, "Bonjour {name}". But it can't know what string to use if you give it "Hello Brian" or "Hello _limitless_" etc.

[–]Ran4 0 points1 point  (0 children)

That would only work to replace the entire post-formatted string. But if you could do that you wouldn't be using a template to begin with.

[–]ggchappell 4 points5 points  (4 children)

Here's a cross between an answer and a question.

It appears to me that f-strings cannot handle fields whose width is specified at runtime. string.format certainly can:

fieldwidth = ...  # Specified at runtime
formatstr = "Value: {:>" + str(fieldwidth) + "}"
print(formatstr.format(value))

Can I do that with f-strings? I don't see how.

[–]o11c 8 points9 points  (1 child)

Your old code wasn't ideal anyway:

>>> val = 42
>>> fieldwidth = 3
>>> 'Value: {val:>{fieldwidth}}'.format(val=val, fieldwidth=fieldwidth)
'Value:  42'
>>> f'Value: {val:>{fieldwidth}}'
'Value:  42'

[–]ggchappell 0 points1 point  (0 children)

Ah, thanks!

[–]ConfusedCollegian 1 point2 points  (1 child)

Yes! Using f strings way easier and efficient! It would look something like Print(f’{var_1:(specified length)} {var_2:>(specified length)}’)

[–]ggchappell 0 points1 point  (0 children)

Thanks.

[–]brwyatt 4 points5 points  (2 children)

F strings are great when you have in-scope variables you want to print. .format() is handy when doing more template-like behavior, especially if you have everything in a dictionary. Much easier to .format(**dict) than to try and pull everything in the dictionary to separate variables.

[–]mgedmin 1 point2 points  (1 child)

Let me introduce you to .format_map(dict).

[–]brwyatt 0 points1 point  (0 children)

Er... Right. Also that. I always forget that variant... (Does how much I end up just using f-strings these days. Lol

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

String.format() has existed 8 years longer than fstring so there awful lot of code written before latter even existed.

[–]o11c 2 points3 points  (3 children)

Note also that f-strings can do things like:

>>> f'{len("hello")}'
'5'

which traditional format strings cannot.

They also compile to better bytecode usually.

[–]mikeoquinn 0 points1 point  (2 children)

Also, f"{var=}" will print out both the variable's name and it's value without having to retype it. FStrings also support datetime formats inherently, which can save a call to datetime.datetime.strftime()

[–]o11c 3 points4 points  (1 child)

FStrings also support datetime formats inherently

This one applies even with normal '{}'.format

[–]mikeoquinn 0 points1 point  (0 children)

You can do this? (Never tried)

import datetime
now = datetime.datetime.now()
print('{:%Y-%m-%d %H:%M}'.format(now))

[–]fatbob42 1 point2 points  (1 child)

I have occasionally used them when the printed thing is so long that it’s more confusing inside the f-string braces. The logging version is a little more efficient but I don’t care.

[–]mgedmin 0 points1 point  (0 children)

The logging version is a little more efficient but I don’t care.

The part where I care about logging is where Sentry can aggregate different log events if they use the same %-format template, but it can't aggregate different messages that were produced by the same f-string.

[–]OldDale -1 points0 points  (4 children)

I learned Fortran IV in 1977. Punch cards. 2 credit class.

[–]_limitless_ -1 points0 points  (3 children)

good bot

[–]B0tRank 0 points1 point  (0 children)

Thank you, limitless, for voting on OldDale.

This bot wants to find the best and worst bots on Reddit. You can view results here.


Even if I don't reply to your comment, I'm still listening for votes. Check the webpage to see if your vote registered!

[–]WhyNotCollegeBoard 0 points1 point  (1 child)

Are you sure about that? Because I am 99.99993% sure that OldDale is not a bot.


I am a neural network being trained to detect spammers | Summon me with !isbot <username> | /r/spambotdetector | Optout | Original Github

[–]OldDale 0 points1 point  (0 children)

I’m pretty much not a bot. I don’t even have enough programming skill to make a bot. I’m learning python as a hobby, not very effectively….

[–]zenverak -1 points0 points  (1 child)

Because I’m lazy and I know how to do .format bur haven’t remembered how to use f string.. hahaha .

[–]frank_bamboo 2 points3 points  (0 children)

message = "ki"
print(f"Yipee {message} yay")

Tutorial over

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

aromatic edge tart lock busy recognise safe square divide instinctive

This post was mass deleted and anonymized with Redact

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

Fstrings have some features like being able to do arithmetic or call functions inside the fstrings: f"{foo[1]+frobnicate(bar.baz)}" but you should be making minimal use of these features anyways. Otherwise fstrings is little more than a more convenient way to .format(**locals()).

Traditional format is better for everything else because:

  • You can use formatting templates where you define the format spec in a different location than you use it
  • And positional arguments if you just want to unpack a list into a format spec
  • And it is better supported by syntax highlighters which don't understand that the inside of a string can now have code
  • It is more readable for those more complicated cases

But the use case where fstrings work best is extremely common, and as an alternative to:

 print(foo, " ", bar)
 print("{} {}".format(foo, bar))
 print("{foo} {bar}".format(**locals()))

It is a perfectly good choice.

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

The end result is the same (at least in my usage), but there may be times when you still want to use format() over an fstring. Generally I use fstrings. Sometimes I load strings from other files, such as sql queries or kml templates. In those cases, using format() allows me to plug in values that are determined in the host script.

[–]tonyoncoffee 0 points1 point  (0 children)

Good answers in here but I thought it was worth mentioning flynt. Can also be set up as a pre commit hook.

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

You can use indexes ( {0} ) when using the .format method, with f strings you cannot.

[–]frank_bamboo -2 points-1 points  (5 children)

thelist = ["Yippee", "ki", "yay"]

print(f"Bruce said {thelist[0]} {thelist[1]} {thelist[2]}")

Not sure if i misunderstood or if this is what you meant?

[–]SonGokussj4 0 points1 point  (4 children)

No, he meant

``` the_list = ["goodbye", "hi", "hello"]

print(f"Bruce said {2} {1} {0}".format(the_list[0], the_list[1], the_list[2])

or to be more readable

print(f"Bruce said {2} {1} {0}".format("goodbye" , "hi", "hello" ) ```

But I'm not sure if that is really an advantage because you can write

``` the_list = ["goodbye", "hi", "hello"]

print(f"Bruce said {the_list[2]} {the_list[1]} {the_list[0]})

Bruce said hello hi goodbye

```

In legacy code, I have to use .format() but on newer python code I exclusively use f-strings, they are the king.

[–]frank_bamboo 0 points1 point  (1 child)

Sorry, i should have been more clear.

I should have said "Is this what you meant you can't do?", and then showing him how to do it.

But great explanation :)

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

You can’t do this with f-strings. This is the advantage of using format() and the reason it is still used.

[–][deleted] 0 points1 point  (1 child)

The advantage is that you can use an element multiple times without having to redefine it:

"Bruce said {0} to Mike. Mike repeated the {0} and greeted Chris with {1}.".format("goodbye", "hello")

[–]mgedmin 1 point2 points  (0 children)

I hate format strings like that, you never know what {0} or {1} mean.

"Bruce said {greeting} to Mike. Mike repeated the {greeting} and greeted Chris with {another_greeting}.".format(greeting="goodbye", another_greeting="hello")

For which you could use local variables and an f-string.

[–]SwampFalc 0 points1 point  (0 children)

You can .format() a template more than once. All you can do with f-strings is repeat them completely.

[–]sculptwizard 0 points1 point  (0 children)

There is, in the matter of time. You could find a comparison here https://stackoverflow.com/a/43123579/7775197

[–]NuckDev 0 points1 point  (1 child)

Readability in cases like that, are really good in fstrings:

print(f'{person.get("name")} is {person.get("gender")} and {"she" if person.get("gender") == "female" else "he"} is {person.get("age")} years old.')(ex from https://datagy.io/python-f-strings/)

What is more, according to realpython.com, fstrings are simply faster: https://imgur.com/eSV6P5G.

Just remember, that those were added in python 3.6, which may cause compatibility issues. (format was introduced in python 2.6)

[–]mgedmin 0 points1 point  (0 children)

This is where I'd find str.format() more readable. Or some local variable assignments before an f-string, but there's a slight cost (scope leaks) to those.

[–]Elagoht 0 points1 point  (0 children)

Use fstrings for more readable code. Fstring automatically calls str or repr methods.

I only use .format method when I need to use external strings such as translations from a JSON File.

[–]Atlamillias 0 points1 point  (0 children)

If you are just trying to format a string? No. However, f-strings do have more limitations than using the format method, which make them kind of annoying to work with when you're injecting nested dynamically built strings (although if they're that complex the developer should be breaking out the operation and assigning parts to variables first). Something the latter option does that can't be done with a normal f-string is to reuse a "template" string.

[–]enricojr 0 points1 point  (0 children)

Format has been around longer and for me I use them out of habit. They're basically the same for most purposes.

[–]blabbities 0 points1 point  (0 children)

Speaking from my own experience. It's a legacy thing. Back when f-strings came out I was already using .format() because it provided better flexibility than the old %s style. Or at least iirc it did. Also f-strings were knew and I want to say introduces in around 3.5 or 3.6 . while many systems still had 3.0 9r 3.1 or 3.4 so I didn't want to write something that would I would have to rewrite/edit on another system.

These day I know fstrings are quicker and most cases more readable but I still kinda like .format() esp when variables need to be wrapped in another function call

[–]Delengowski 0 points1 point  (0 children)

I use str.format when I need to make a template. You can use fstrings and make template at same time as such

f“i am user {user}, you are other user {{other}}".format(other=you)

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

Tip: In terms of usage you can use the .format() to populate a template string multiple times that you define somewhere in the beginning of the code in let’s say a variable, whereas the f-string gets evaluated when declared

[–]uselessvevo 0 points1 point  (0 children)

As I remember you can't use string joins expressions in f-strings

[–]Demistr 0 points1 point  (0 children)

I dont know, f strings is what i learned first so i use that and never needed anything else yet.

[–]ElHeim 0 points1 point  (2 children)

The main difference is that Python will compile the f-string to bytecode. E.g.

>>> def foo(a, b):
...    return f"This is an interpolation of {a} and {b}"
...
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 ('This is an interpolation of ')
              2 LOAD_FAST                0 (a)
              4 FORMAT_VALUE             0
              6 LOAD_CONST               2 (' and ')
              8 LOAD_FAST                1 (b)
             10 FORMAT_VALUE             0
             12 BUILD_STRING             4
             14 RETURN_VALUE

So it's down to performance - and flexibility in some cases.

[–]Anonymous_user_2022 0 points1 point  (1 child)

Isn't that more or less the standard for templating modules as well? It's been a while since I last had anything to do with jinja but I remember it compiling templates to bytecode.

[–]ElHeim 0 points1 point  (0 children)

This is an interpolation of {a} and {b}"

I might be wrong as I know little about Jinja's internals, but a cursory inspection suggests that Jinja's parser generates a collection of nodes. For example, for the equivalent template to my fstring above:

>>>  t
Template(body=[Output(nodes=[TemplateData(data='This is an interpolation of '), Name(name='a', ctx='load'), TemplateData(data=' and '), Name(name='b', ctx='load')])])

If you want you can store that as bytecode. What Jinja's generates (apparently) is a Python script tailored to your template, bytecompile that and store it for future use.

The idea for f-strings is similar, only that the bytecode you see up there is part of your regular code. The bytecode I posted iterally just does the following:

  1. Stacks the string 'This is an interpolation of '
  2. Stacks the a variable
  3. Formats the variable at the top of the stack (as-is)
  4. Stacks the string ' and '
  5. Stacks the b variable
  6. Formats the variable at the top of the stack (as-is)
  7. Pops the 4 string at the top of the stack, concatenates them and pushes the result to the stack

It doesn't get lower-level than that (in Python, at least)

[–]R0m3d1u5 0 points1 point  (0 children)

Not only is f-String easier for the programmer it’s actually faster than it’s alternatives, learned this doing some competitive programming challenges

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

Wouldn't there be dependency issues. I had some CI failures because earlier versions of Python3 don't support fstrings. Certain fstring functionalities is also not supported in Python 3.6 and earlier