How to postpone/defer the evaluation of f-strings? How to postpone/defer the evaluation of f-strings? python python

How to postpone/defer the evaluation of f-strings?


Here's a complete "Ideal 2".

It's not an f-string—it doesn't even use f-strings—but it does as requested. Syntax exactly as specified. No security headaches since we are not using eval().

It uses a little class and implements __str__ which is automatically called by print. To escape the limited scope of the class we use the inspect module to hop one frame up and see the variables the caller has access to.

import inspectclass magic_fstring_function:    def __init__(self, payload):        self.payload = payload    def __str__(self):        vars = inspect.currentframe().f_back.f_globals.copy()        vars.update(inspect.currentframe().f_back.f_locals)        return self.payload.format(**vars)template = "The current name is {name}"template_a = magic_fstring_function(template)# use it inside a function to demonstrate it gets the scoping rightdef new_scope():    names = ["foo", "bar"]    for name in names:        print(template_a)new_scope()# The current name is foo# The current name is bar


This means the template is a static string with formatting tags in it

Yes, that's exactly why we have literals with replacement fields and .format, so we can replace the fields whenever we like by calling format on it.

Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string

That's the prefix f/F. You could wrap it in a function and postpone the evaluation during call time but of course that incurs extra overhead:

template_a = lambda: f"The current name is {name}"names = ["foo", "bar"]for name in names:    print (template_a())

Which prints out:

The current name is fooThe current name is bar

but feels wrong and is limited by the fact that you can only peek at the global namespace in your replacements. Trying to use it in a situation which requires local names will fail miserably unless passed to the string as arguments (which totally beats the point).

Is there any way to bring in a string and have it interpreted as an f-string to avoid using the .format(**locals()) call?

Other than a function (limitations included), nope, so might as well stick with .format.


A concise way to have a string evaluated as an f-string (with its full capabilities) is using following function:

def fstr(template):    return eval(f"f'{template}'")

Then you can do:

template_a = "The current name is {name}"names = ["foo", "bar"]for name in names:    print(fstr(template_a))# The current name is foo# The current name is bar

And, in contrast to many other proposed solutions, you can also do:

template_b = "The current name is {name.upper() * 2}"for name in names:    print(fstr(template_b))# The current name is FOOFOO# The current name is BARBAR