嵌套的 f-strings

92
感谢David Beazley的推文,我最近发现新的Python 3.6 f-strings也可以嵌套使用:
>>> price = 478.23
>>> f"{f'${price:0.2f}':*>20s}"
'*************$478.23'

或者:

>>> x = 42
>>> f'''-{f"""*{f"+{f'.{x}.'}+"}*"""}-'''
'-*+.42.+*-'

虽然我对这种可能性感到惊讶,但我不知道它在实际中有多少实用性,嵌套f-strings什么时候会有用?这可以涵盖哪些用例?

注意:PEP本身没有提到嵌套f-strings,但有一个具体测试用例


5
可能是出于与嵌套旧的 str.format 相同的目的:https://dev59.com/L5vga4cB1Zd3GeqP9fB4 - TigerhawkT3
1
这里还有一个不错的例子。关于是否重复,我将把决定留给您。 - TigerhawkT3
1
@TigerhawkT3,感谢您提供的好例子!我不确定这些是否是直接重复,但肯定是相关的 - 关于关闭..我会接受社区决定的任何结果。我也希望可能有一些关于f-strings的具体内容。我们应该给这个话题一些时间和机会。 - alecxe
6
不确定是否值得回答,但是现在可以更容易地在一行中打印金字塔了 print("\n".join(f'{a:{a}<{a}}' for a in range(1,10))) - Bhargav Rao
2
我之前为计时器制作了这个怪物:f'''已设置计时器为{f"{hrs:02d}:{mins:02d}:{secs:02d}" if hrs > 0 else f"{f'{mins:02d}:{secs:02d}' if mins > 0 else f'{secs}秒'}"}!''' - kr8gz
显示剩余2条评论
15个回答

95

我认为允许格式化字符串字面值嵌套(嵌套是指f'{f".."}')并不是因为考虑了可能的用例,而更多地是允许它们符合其规范。

规范说明它们可在括号内支持完整的Python表达式(参见链接1)*。还表示格式化字符串字面值实际上只是一个在运行时计算的表达式(请参阅这里这里)。因此,允许将格式化字符串字面值作为另一个格式化字符串字面值内部的表达式是有意义的,禁止它会否定对Python表达式的全面支持。

你无法在文档中找到用例的原因是,这可能只是实现的一种好(副)作用,而不是其激励用例。


实际上,有三个例外:不允许空表达式,lambda表达式必须用明确的括号括起来。使用相同引号嵌套字符串会返回语法错误。

2
我想你是对的,完全同意。今天的赞已经用完了 - 明天再来。谢谢。 - alecxe
@alecxe 我非常确定,在某个时候,一些涉及到 f-string 嵌套的古怪事情会出现。 :-) - Dimitris Fasarakis Hilliard
是的,我刚遇到一个情况,我需要嵌套 f"..." 并且我很高兴能这样做。这是 Python 非常好用的另一个原因! - Jens
1
我不能在f-strings中放置带引号的字符串,更不用说其他的f-strings了。 - rjurney
三个例外:Python 中不支持同引号嵌套,例如 f"{f"foo"}" 是语法错误。在 JavaScript 中可以这样做,因此这是 Python 的缺陷。 - silverwind

20
我猜这是为了在同一行中传递格式化参数,从而简化的使用。
例如:
>>> import decimal
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"
'result:      12.35'

当然,它允许程序员编写完全无法阅读的代码,但这并不是它的目的。

2
是的!str.format一直支持这个功能,例如'{0:.{1}f}'.format(math.pi, 4)就是'3.1416'。如果f-string不支持这个功能,那就太糟糕了。 - wim
7
你的例子并没有展示嵌套的f-string,只有嵌套的花括号。 - Robert Nowak

10

我刚刚遇到了类似的问题(我认为是这样),所以想分享一下。

我的具体情况是有一个大型、复杂的SQL语句,在其中需要有一些条件性非常不同的值,但有些f字符串却相同,并且在其他地方也被使用。

以下是一个快速示例,我选择的列是不变的(并且在其他查询中也被使用),但表名取决于组名,无法通过循环实现。

每次都需要在str2中包含mycols=mycols感觉有点不太好,尤其当我有多个这样的参数时。

我不确定这是否有效,但很高兴它确实起作用了。至于它是否符合Python风格,我真的不太确定。

mycols='col_a,col_b'

str1 = "select {mycols} from {mytable} where group='{mygroup}'".format(mycols=mycols,mytable='{mytable}',mygroup='{mygroup}')

group = 'group_b'

if group == 'group_a':
    str2 = str1.format(mytable='tbl1',mygroup=group)
elif group == 'group_b':
    str2 = str1.format(mytable='a_very_different_table_name',mygroup=group)

print(str2)

2
在第3行的format中,您可以在字符串文字中使用{{my_table}},而不是将{my_table}替换为my_table。然后,format会将双括号转换为单个括号。因此,您可以有更短的代码:str1 = "select {mycols} from {{mytable}} where group='{{mygroup}}'".format(mycols=mycols) - Arseny

7

任何基本用例都是需要一个字符串来完整描述您想要放入f-string花括号{}中的对象。例如,您需要字符串来索引字典。

因此,在我的ML项目中,我使用了它,代码如下:

scores = dict()
scores[f'{task}_accuracy'] = 100. * n_valid / n_total
print(f'{task}_accuracy: {scores[f"{task}_accuracy"]}')

是的!在字符串内部进行字典查找是嵌套f-strings的一个很好的例子。 - bfris

4

我用它来格式化货币。给出像下面这样的值:

a=1.23
b=45.67

将它们格式化为带有前导$符号,并使小数点对齐。例如:

 $1.23
$45.67

使用单个f字符串f"${value:5.2f}"进行格式化,您可以得到:

$ 1.23
$45.67

有时候使用简单的 f-strings 是可以的,但并不总是适用。嵌套的 f-strings f"${f'${value:.2f}':>6}" 可以给你精确的格式:

 $1.23
$45.67

3

在做个人项目的过程中,我因写自己的数据库库而分心。其中一个发现是:

>>> x = dict(a = 1, b = 2, d = 3)
>>> z = f"""
    UPDATE TABLE 
        bar 
    SET 
        {", ".join([ f'{k} = ?'     for k in x.keys() ])} """.strip()
>>> z
'UPDATE TABLE 
    bar 
SET 
    a = ?, b = ?, d = ?  '

我也对此感到惊讶,老实说,我不确定我会在生产代码中做这样的事情,但我也曾说过我不会在生产代码中做很多其他的事情。


“我被编写自己的数据库库分心了” 哈哈哈 :) 是的,这很有趣,但我绝不会在生产中使用它 :) - Christopher Mahan
1
@ChristopherMahan 我几年前就退休了,所以我有时间去探索一些不好的想法。如果你感兴趣,可以看看 https://github.com/devdave/dcdb。虽然还有很多功能缺失,但没关系,因为我有时间去实现它们,或者放弃并回到 sqlalchemy。 - David

3

我发现在编写三元运算符时使用嵌套很有用。阅读性因人而异,但我认为这个一行式的写法非常实用。

logger.info(f"No program name in subgroups file. Using {f'{prg_num} {prg_orig_date}' if not prg_name else prg_name}")

因此,我进行嵌套测试的标准如下:

  • 变量是否被重新使用?(表达式重用)
  • 表达式是否清晰?(不超过复杂度限制)

2

一个简单的例子,说明它何时有用,以及实现的例子:有时格式化也是一个变量。

num = 3.1415
fmt = ".2f"
print(f"number is {num:{fmt}}")

2
以下的嵌套f-string一行代码很好地构建了一个命令参数字符串: cmd_args = f"""{' '.join([f'--{key} {value}' for key, value in kwargs.items()])}""" 其中输入:{'a': 10, 'b': 20, 'c': 30, ....}
优雅地转换成了 --a 10 --b 20 --c 30 ....

这与未嵌套版本cmd_args = ' '.join(f'--{key} {value}' for key, value in kwargs.items())有何不同? - rici

2

嵌套f-strings与格式说明符中的求值表达式

这个问题是关于使用f-string在“外部”f-string的某个求值表达式内部的用例。

这与允许在f-string的格式说明符中出现求值表达式的特性不同。后者非常有用,与此问题有些相关,因为(1)它涉及嵌套花括号,所以人们可能会看这篇文章,(2)嵌套f-strings在格式说明符中与它们在f-string的其他花括号表达式中一样被允许。

F-string嵌套可以帮助简化代码

虽然肯定不是允许嵌套f-strings的主要动机,但在需要或想要“一行代码”的晦涩情况下可能会有所帮助(例如lambda表达式、推导式、从终端的python -c命令)。例如:

print('\n'.join([f"length of {x/3:g}{'.'*(11 - len(f'{x/3:g}'))}{len(f'{x/3:g}')}" for x in range(10)]))

如果您不需要一个一行代码,任何语法嵌套都可以通过先定义一个变量,然后在f-string的评估表达式中使用变量名来替换(在许多情况下,非嵌套版本可能更易读且更易于维护; 但是这需要想出变量名):

for x in range(10):
    to_show = f"{x/3:g}"
    string_length = len(to_show)
    padding = '.' * (11 - string_length)
    print(f"length of {to_show}{padding}{string_length}")

嵌套的计算表达式(即在格式说明符中)是有用的

与真正的f-string嵌套相比,允许在f-string的“格式说明符”中使用计算表达式的相关特性非常有用(正如其他人指出的那样),原因包括:

  1. 格式化可以在多个f-string或计算表达式之间共享
  2. 格式化可以包括可以在每次运行时变化的计算数量

这里是一个使用嵌套计算表达式而不是嵌套f-string的示例:

import random

results = [[i, *[random.random()] * 3] for i in range(10)]
format = "2.2f"

print("category,precision,recall,f1")
for cat, precision, recall, f1 in results:
    print(f"{cat},{precision:{format}},{recall:{format}},{f1:{format}}")

然而,即使这种嵌套的用法可以被替换为更灵活(也许更干净)的代码,而不需要语法嵌套。
import random

results = [[i, *[random.random()] * 3] for i in range(10)]
def format(x):
    return f"{x:2.2f}"

print("category,precision,recall,f1")
for cat, precision, recall, f1 in results:
    print(f"{cat},{format(precision)},{format(recall)},{format(f1)}")

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