How to postpone/defer the evaluation of f-strings?
f-string, python, string-formatting, string-interpolation
Solution
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
Problem
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} ``` See also: How can I use f-string with a variable, not with a string literal?