F-string by default?

Have we considered a from __future__... import to have all strings in the file work as f-strings without needing the f-prefix?

Example:

from __future__ import f_string_by_default

a = 'hello'

print('{a} world')

^ prints: “hello world”

Is there any future where this type of thing could be default Python behavior? I personally see it as a ‘Python 4’ type thing since it could break existing code… but we could probably do a __future__ import now if we wanted.

Is the change a requirement: No
Is it because I forget to put the ‘f’ in front of f-strings: Yeah :confused:

3 Likes

That’d break a LOT of code.

1 Like

The idea of from future is to make available what will be a future default.

Given that this idea will be a breaking change that would cause my code to stop working I have to say its a non starter.

I presented a proposal on this at the language summit a few years ago. No one was very excited about it, for the obvious reason that it’s a breaking change.

Part of my proposal was to add “p-strings”, or plain strings, for literals you didn’t want treated as f-strings.

I consider the proposal dead.

2 Likes

I don’t think having to type an “f” prefix at the start of your string is an onerous requirement, and it mitigates against the risk of errors.

If f-strings were limited to only evaluating named variables, I would be prepared to entertain the idea that changing the default behaviour might be a good idea. But they aren’t, f-strings can evaluate arbitrary expressions, which makes them dangerous.

print("Set containing the number googleplex: {10**(10**100)}")  # Clearly safe.
print(f"Set containing the number googleplex: {10**(10**100)}") # Not so much.

We want the safe string (pure data, no code being evaluated) to be the default, and the “unsafe” string (may evaluate code) to require an explicit opt-in.

The status quo reduces errors: it is harder to accidentally add an f prefix to a string that shouldn’t have one, than it is to forget to escape braces that should be escaped.

The status quo also fails safe: if you forget to include the f prefix on a string that is supposed to have one, you just get some pretty obvious data corruption. Whereas if you forget to escape something bad, it will fail badly.

1 Like

Of a form that frequently ends up getting through to production, and getting showcased on The Daily WTF.

Could a (opt in) warning be generated if curly braces are used but the string isn’t an f-string?

Then again this is available by linters already… more/less. I’ve wished before that a warning would be written out to catch these errors on the fly. Though I guess there isn’t an easy way to know if it was intended or not… and then if you want to opt out of the warning it sounds sort of like a linter again.

Almost certainly not. The very reason that they’re used that way in f-strings is that they are the same syntax as the str.format method. So, again, it’s going to catch huge amounts of very valid code.

And no, you can’t deprecate str.format() in favour of f-strings, for quite a number of reasons.

But even if it weren’t a backward-compatibility concern on the basis of format directives, having string literals start containing code will cause myriad problems… and for what gain? The ability to write f-strings without putting f in front of them?

My thoughts are more about forgetting the f than disallowing not using it. It’s fine to require it… I just find that sometimes I miss it and not notice the miss till later (in a log or something without an evaluation done). I’d imagine some beginners may miss it too but don’t have data for that.

Technically it could see if format is going to be called to determine if the curly braces would be used. Even that though would be clunky, slow, and funky. Just thinking about how that would work gives me a minor headache.

Maybe a rainy day exercise for me would be to write a module that just rewrites the ‘current’ module strs to fstrs via the AST… Throw it onto pypi and call it a day.

How? What if the format method isn’t being called on a literal?

Check if string has format called, early evaluate it then check for curlys of the result.

Hmm, okay. Seems easy enough. Let’s try this.

name = "Subject Name Here" # from Subject Hometown Here
args = {"name": name}
print("Hello, {name}!".format_map(args))
greeting = "Hello, {name}!"
print(greeting.format_map(args)

I’m sure you can figure that one out, simple matter of static analysis. All you have to do is trace through everywhere that a string gets used. I’m sure that won’t cause any weird, confusing, annoying edge cases. It’ll be fine. Ooh, what about this one?

def fmt(msg, **args):
    return msg.format_map(args)

def main():
    name = input(fmt("Enter your name: "))
    print(fmt("Hello, {name}!", name=name))

Hmm. Does it become any harder if we destubbify that function a bit?

# this would probably come from a YAML file or something
# assume that, after parsing, it's a mapping from original English to
# the chosen language - even if that's English
from translations import english as lang
def L10N(msg, **args):
    msg = lang.get(msg, msg)
    return msg.format_map(args)

def main():
    name = input(L10N("Enter your name: "))
    print(L10N("Hello, {name}!", name=name))

Remember, you’re asking the Python compiler to magically know which ones should be compiled as evaluated expressions and which ones should be literals. This can be incredibly significant:

def outer(name):
    print(name)
    msg1 = "I am {name}!"
    def inner():
        msg2 = "And I am {name}!"
        print(msg1.format(name="inner"))
        print(msg2.format(name="inner"))
    return inner

outer()()

You can’t change the way a string is parsed on the basis of whether its format method will, in the future, be called. You definitely can’t change the way it’s parsed on the basis of whether the format method MIGHT be called, unless you have some way of absolutely guaranteeing what every module does - including modules you haven’t seen yet. In most of these examples, you could break this across two modules, making the task pretty much impossible.

This kind of thing might (MIGHT) be possible in C++, with all of the potential confusion being resolved at link time. I don’t think it will work in Python. Feel free to prove me wrong, though.

1 Like

You are converting runtime code into precompiled code. As Chris shows that is not a good idea.