Reputation: 22324
When using formatted string literal, it is possible to have nested f-strings to some extent.
a = 3
b = 7
res = f"{f'{a*b}'}"
print(res) # '21'
Although, the same does not work if the inner expression is a variable containing a string.
a = 3
b = 7
expr = 'a*b'
res = f"{f'{expr}'}"
print(res) # 'a*b'
Is there a way to make this work and to have the second output to be '21'
as well? If not, what is the difference between the first and second string that prevents it?
Upvotes: 4
Views: 906
Reputation: 51165
I think it can be helpful to see what is actually happening under the hood when each of these expressions is called.
f"{f'{a*b}'}"
def om1(a, b):
return f"{f'{a*b}'}"
dis.dis(om1)
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_MULTIPLY
6 FORMAT_VALUE 0
8 FORMAT_VALUE 0
10 RETURN_VALUE
The outer f-string encounters an expression which it evaluates, and the inner f-string also finds an expression which it evaluates, which results in the call to BINARY_MULTIPLY
f"{f'{expr}'}"
def om2(a, b):
expr = 'a*b'
return f"{f'{expr}'}"
dis.dis(om2)
2 0 LOAD_CONST 1 ('a*b')
2 STORE_FAST 2 (expr)
3 4 LOAD_FAST 2 (expr)
6 FORMAT_VALUE 0
8 FORMAT_VALUE 0
10 RETURN_VALUE
Here, the first f-string encounters an expression and evaluates it, and the inner f-string, encounters a string, resulting in the call to LOAD_FAST
instead of evaluating the contents of the string as Python code.
Also it is important to note in this second example the missing LOAD_FAST
calls to both a
and b
, which are present in the first example.
Upvotes: 2
Reputation: 198446
It's called "string literal interpolation". The string must be a literal, i.e. at the time the compilation takes place compiler will turn the string into a proper executable code. If you already have a string as a value (not as a literal), it's too late for that.
I don't have access to Python that has PEP 498 enabled, so my examples will be in Ruby, which has had this mechanism for a long time. The Ruby syntax for Python's f"...{expr}..."
is "...#{expr}..."
.
In Ruby, "a#{2 * 3}b"
is syntactic sugar for ["a", (2 * 3), "b"].join
(as in, they produce exactly the same bytecode). If you have the string "2 * 3"
already as a value, the compiler can't do anything about it; the only way to turn a string value into a result is to evaluate it.
In the first example, you have a string literal inside a string literal; both are processed by the compiler at compile time: when the compiler sees the outer literal, it compiles it, finds another string literal there, compiles that as well, produces code. As a matter of fact, "a#{"#{2 * 3}"}b"
produces exactly the same byte code, again.
The fact that this is done at compile time is also the reason why string literal interpolation will raise a syntax error if the expression inside is malformed, even if the line in question is never executed: if false; "#{1+}"; end
will produce a SyntaxError
.
The fact that this is done at compile time means strings already in variables are not eligible for this mechanism. In your code, by the time res
is evaluated, expr
could have been anything; the only way out is evil
(or another, safer, evaluator).
Upvotes: 2
Reputation: 402922
There are a few libraries that have developed functions for evaluating numerical and logical expressions safely ("safe" being the key).
First, the setup -
a = 3
b = 7
op = '*'
numexpr.evaluate
>>> import numexpr as ne
>>> ne.evaluate(f'{a} {op} {b}')
array(21, dtype=int32)
numexpr
is smart enough to optimise your expressions, and is even faster than numpy in some instances. Install using pip
.
pandas.eval
A safe eval from the Pandas API similar to ne.evaluate
.
>>> import pandas as pd
>>> pd.eval(f'{a} {op} {c}')
12
Upvotes: 3