嵌套字符串文字插值是可能的吗?

4

使用格式化字符串字面值时,可以在一定程度上嵌套f-strings

a = 3
b = 7

res = f"{f'{a*b}'}"

print(res) # '21'

然而,如果内部表达式是包含字符串的变量,则该方法无法奏效。

a = 3
b = 7

expr = 'a*b'

res = f"{f'{expr}'}"

print(res) # 'a*b'

有没有办法使它工作,并且使第二个输出也是'21'?如果没有,是什么区别导致第一个和第二个字符串不同?


只是想澄清一下,您是否想知道如何使第二个输出为21? - user3483203
1
也许你可以放一个 eval,即 res = f"{f'{eval(expr)}'}" - Tobias Kienzler
2
如果有人发布了一个带有 eval 的答案,他们将会被踩。 - user3483203
1
@user3483203 我接受你的赌注,并加码两个选项。 - cs95
1
有点相关:https://dev59.com/OFgR5IYBdhLWcg3whtqk - AGN Gazer
显示剩余2条评论
3个回答

3

有一些库开发了函数来安全地评估数值和逻辑表达式("safe"是关键词)。

首先,进行设置 -

a = 3
b = 7
op = '*'

numexpr.evaluate

>>> import numexpr as ne
>>> ne.evaluate(f'{a} {op} {b}')
array(21, dtype=int32)

numexpr 能够优化您的表达式,在某些情况下甚至比numpy更快。使用 pip 进行安装。


pandas.eval

Pandas API 中的一个安全 eval,类似于 ne.evaluate

>>> import pandas as pd
>>> pd.eval(f'{a} {op} {c}')
12

十分钟前不是你发了誓言要对使用 eval 的任何内容进行负评吗? - Olivier Melançon
2
@OlivierMelançon 内置的 eval,没错。而且我仍然会。 - cs95
公平的说,如果实际上不能使用f-strings,我更想知道为什么不能这样做,但是你的答案很有意思。为什么pandas.eval被认为是安全的? - Olivier Melançon
1
@OlivierMelançon 为什么它不起作用?我不确定,但我可以回答第二个问题。pandas.eval实际上有代码来解析它接收到的表达式,并且它对于认为有效和无效的操作类型非常严格。例如,将导入语句传递给pd.eval是绝对不允许的。 - cs95

2

我认为,在调用每个表达式时,了解其实际发生的情况可能会很有帮助。

f"{f'{a*b}'}"

这个表达式是嵌套的f-string,它将a与b相乘的结果作为内部f-string的表达式,并将其格式化为字符串。最终得到的字符串是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

外部的f-string遇到一个需要计算的表达式,内部的f-string也发现了需要计算的表达式,这导致调用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

在这里,第一个f-string遇到了一个表达式并对其进行评估,而内部的f-string则遇到了一个字符串,导致调用LOAD_FAST而不是将字符串内容作为Python代码进行评估。

此外,值得注意的是,在第二个示例中,缺少对第一个示例中存在的abLOAD_FAST调用。


显示字节码是一个好主意。这个答案非常有帮助,但如果不是由@Amadan回答中给出的关键事实——f-string代码是在编译时生成的,而不是在运行时生成的,并且它并不依赖于调用eval,就不太够了。如果我能接受两个答案,我会这样做,因为结合起来它们给出了最好的解释。 - Olivier Melançon

2
它被称为“字符串文字插值”。该字符串必须是字面量,也就是编译时编译器将把字符串转换为适当的可执行代码。如果您已经有一个字符串作为值(而不是作为字面量),那么现在为时已晚。
我无法访问启用了PEP 498的Python,因此我的示例将使用Ruby,它已经有这个机制很长时间了。Python中f"...{expr}..."的Ruby语法是"...#{expr}..."
在Ruby中,"a#{2 * 3}b"["a", (2 * 3), "b"].join的语法糖(也就是说,它们产生完全相同的字节码)。如果您已经将字符串"2 * 3"作为值,则编译器无法对其进行任何处理;将字符串值转换为结果的唯一方法是评估它。
在第一个示例中,您有一个字符串字面量在另一个字符串字面量内部;两者都由编译器在编译时处理:当编译器看到外部字面量时,它会编译它,找到另一个字符串字面量,也将其编译,生成代码。事实上,"a#{"#{2 * 3}"}b"再次产生完全相同的字节码。
这是在编译时完成的事实,也是为什么如果内部表达式格式不正确,字符串文字插值将引发语法错误,即使该行永远不会被执行:if false; "#{1+}"; end将产生SyntaxError
这是在编译时完成的事实意味着已经在变量中的字符串不符合此机制的条件。在您的代码中,当res被评估时,expr可能已经是任何东西;唯一的出路是evil(或另一个更安全的求值器)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接