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

Question

Asked By – JDAnders

I am using template strings to generate some files and I love the conciseness of the new f-strings for this purpose, for reducing my previous template code from something like this:

template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print (template_a.format(**locals()))

Now I can do this, directly replacing variables:

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

However, sometimes it makes sense to have the template defined elsewhere — higher up in the code, or imported from a file or something. This means the template is a static string with formatting tags in it. Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string, but I don’t know if there is such a thing.

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

Ideally I want to be able to code like this… (where magic_fstring_function is where the part I don’t understand comes in):

template_a = f"The current name is {name}"
# OR [Ideal2] template_a = magic_fstring_function(open('template.txt').read())
names = ["foo", "bar"]
for name in names:
    print (template_a)

…with this desired output (without reading the file twice):

The current name is foo
The current name is bar

…but the actual output I get is:

The current name is {name}
The current name is {name}

Now we will see solution for issue: How to postpone/defer the evaluation of f-strings?


Answer

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 inspect

class 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 right
def 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 question is answered By – Paul Panzer

This answer is collected from stackoverflow and reviewed by FixPython community admins, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0