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 thatstr.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' callsrepr()
, and '!a' callsascii()
.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:
- Declare
"Ben"
to variable namedreplace
, and - 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
Change the Color of Text Within a Pandas Dataframe HTML Table Python Using Styles and CSS
Why Does Pyimport_Import Fail to Load a Module from the Current Directory
Conda Reports Packagesnotfounderror: Python=3.1 for Reticulate Environment
Why am I Getting "Indentationerror: Expected an Indented Block"
Send File Using Post from a Python Script
Opencv Giving Wrong Color to Colored Images on Loading
Remove All Special Characters, Punctuation and Spaces from String
How to Include Third Party Python Libraries in Google App Engine
How to Avoid Python/Pandas Creating an Index in a Saved CSV
Does Anybody Know How to Identify Shadow Dom Web Elements Using Selenium Webdriver
How to Install and Import Python Modules at Runtime
Computing Cross-Correlation Function
Combine Two Pandas Data Frames (Join on a Common Column)
Python Date String to Date Object
Transpose Column to Row with Spark