How to Postpone/Defer the Evaluation of F-Strings

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 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

Using f-strings as templates

No, you can't store a f-string for deferred evaluation.

You could wrap it in a lambda, but that's not really any more readable imo:

template = lambda *, name, age, colour: f"Hello, my name is {name} and I'm {age} years old, my favourite colour is {colour}"
# ...
print(template(name="John", age="30", colour="Red"))

What is the name of the internal function that executes an f-string?

Is there an internal Python function (among the many double underscode __something__ functions), that defines how the interepreter "runs" / "interpolates" a f-string

The answer is no. From PEP 498 which introduces the f-strings:

The exact code used to implement f-strings is not specified. However, it is guaranteed that any embedded value that is converted to a string will use that value's __format__ method. This is the same mechanism that str.format() uses to convert values to strings.

And from the docs about Lexical Analysis in Formatted string literals section:

If a conversion is specified, the result of evaluating the expression is converted before formatting. Conversion '!s' calls str() on the result, '!r' calls repr(), and '!a' calls ascii().

The result is then formatted using the format() protocol. The format specifier is passed to the __format__() method of the expression or conversion result. An empty string is passed when the format specifier is omitted. The formatted result is then included in the final value of the whole string.

So nothing new is implemented for them. They are built on top of existing protocols.

Can I delay evaluation of the python expressions in my f string?

As pointed out in the comments, arguments are evaluated eagerly, f-strings included. To make the evaluation lazy, The logging functions accept *args and **kwargs for example, as explained here.


Now, a clean way would be using strings that are subsequently format()ed instead of f-strings, but if f-string are just too much convenient you can still use lambdas (only when lazy-evaluation is needed). You could change the debug_log function in something like this:

def debug_log(log):
if not debug: return
str_log = log() if callable(log) and log.__name__ == '<lambda>' else log
print(str_log, file=sys.stderr)

A few examples should make everything more clear.



Eager evaluation is good

If you need to print an argument that does not need to be lazily evaluated, then nothing is different from before:

>>> debug = False
>>> debug_log('hello')
>>> debug = True
>>> debug_log('hello')
hello

Lazy evaluation necessary

Now suppose you have a function like this one:

def test():
print('please print me if necessary')
return 'done'

If called inside debug_log as-is, then

>>> debug = False
>>> debug_log(test())
please print me if necessary

But, of course, you do not want to see please print me if necessary because debug = False. So, the clean way would be:

>>> debug = False
>>> debug_log(lambda: f'we are {test()}')
>>> debug = True
>>> debug_log(lambda: f'we are {test()}')
please print me if necessary
we are done

Basically, by enclosing an f-string inside a lambda, you can be sure that it is executed only when the lambda is actually called.

How to make string f-string after variable initiazation?

You need:

str1 = 'date time for {val1} and {val2}'

val1 = 1
val2 = 77

print(eval(f"f'{str1}'"))

Is it possible to reuse an f-string as it is possible with a string and format?

You can't.

An f-string isn't a kind of string, it's a kind of string literal, which is evaluated immediately. You can't store an f-string in a variable to be evaluated later, or accept one from a user, etc.1 This is the only reason that they're safe.

So, what if you do want to use a format multiple times (or one taken from a user, etc.)? You use str.format.

Occasionally, you need to capture all of the locals and globals the same way an f-string does, but to do it explicitly. Because this is a rare case (and potentially a security hole), it's intentionally a bit ugly:

TEXT_AMOUNT = 'text {amount}'

def f1(beginning):
amount = 100
return beginning + TEXT_AMOUNT.format(**locals(), **globals())

This makes you think about what you're writing—you don't really want globals here, right? So leave it off. And it also signals the reader—if you're pulling in locals, they'll want to see that the string really is a constant in your source that isn't doing anything dangerous.


1. Well, you could use an f-string inside a string that you pass to eval… but that's a terrible idea.

Replace word in string using f-string

Better option to achieve this will be using str.format as:

>>> x = "this is {replace}`s mess"
>>> x.format(replace="Ben")
'this is Ben`s mess'

However if it is must for you to use f-string, then:

  1. Declare "Ben" to variable named replace, and
  2. Declare x with f-string syntax

Note: Step 1 must be before Step 2 in order to make it work. For example:

>>> replace = "Ben" 
>>> x = f"this is {replace}`s mess"
# ^ for making it f-string

>>> x
'this is Ben`s mess' # {replace} is replaced with "Ben"


Related Topics



Leave a reply



Submit