我该如何使用f-string与变量一起使用,而不是字符串文字?

76

我想使用 f-string 与我的字符串变量一起使用,而不是与一个字符串常量 "..." 一起使用。

以下是我的代码:

name=["deep","mahesh","nirbhay"]
user_input = r"certi_{element}" # this string I ask from user  

for element in name:
    print(f"{user_input}")

这段代码将输出:

certi_{element}
certi_{element}
certi_{element}

但是我想要:

certi_{deep}
certi_{mahesh}
certi_{nirbhay}

我该怎么做?


6
你不希望用户能够执行任意代码,将其改为字典查找或类似方式。 - user3483203
2
@user3483203:用户在哪里表明他们希望能够使用任意变量?这里没有任何说明只能使用任何名称。 - Martijn Pieters
1
@MartijnPieters 我的评论与任意变量无关。我是说他们不应该希望对用户输入中的表达式进行评估。 - user3483203
2
@user3483203:没错,我不知道原帖作者是否期望完全的表达式支持(或者甚至意识到这个功能的存在)。他们正在询问一个变量,因此 str.format() 完全足够。 - Martijn Pieters
如何推迟/延迟f-strings的评估? - wim
5个回答

80
< p > f"..." 字符串适用于将表达式结果插入文字字面量中,但您没有文字字面量,而是在单独的变量中使用模板字符串。

您可以使用 str.format()将值应用于该模板:

name=["deep","mahesh","nirbhay"]
user_input = "certi_{element}" # this string i ask from user  

for value in name:
    print(user_input.format(element=value))

使用名称作为占位符(例如{element})的字符串格式化占位符不是变量。您需要在str.format()函数的关键字参数中为每个名称分配一个值来填充占位符。在上面的示例中,element=valuevalue变量的值传递给占位符中的element

f-strings不同,{...}占位符不是表达式,您不能在模板中使用任意Python表达式。这是件好事情,因为您不希望终端用户能够在您的程序中执行任意Python代码。有关详细信息,请参阅格式化字符串语法文档

您可以传递任意数量的名称;字符串模板不一定要使用其中任何一个。如果将str.format()**mapping调用约定结合使用,则可以使用任何字典作为值的来源:

template_values = {
    'name': 'Ford Prefect',
    'number': 42,
    'company': 'Sirius Cybernetics Corporation',
    'element': 'Improbability Drive',
}

print(user_input.format(**template_values)

上述方法允许用户在模板中使用template_values中的任意名称,可以使用任意次数。

虽然您可以使用locals()globals()生成将变量名称映射到值的字典,但我不建议使用该方法。请使用像上面示例中的专用命名空间来限制可用名称,并为最终用户记录这些名称。


感谢您的清晰解释。 - Deep Ghodasara
如果我不知道用户在 { } 中输入了什么怎么办?在我们的例子中:element。 - Deep Ghodasara
@DeepGhodasara:你仍然需要告诉用户使用特定的名称。虽然你可以解析模板中使用的名称,但你仍然需要将这些名称映射到相应的值。我已经添加了如何让某人访问他们可以选择的多个名称的说明。 - Martijn Pieters

47

如果你定义:

def fstr(template):
    return eval(f"f'{template}'")

然后你可以执行:

name=["deep","mahesh","nirbhay"]
user_input = r"certi_{element}" # this string i ask from user  

for element in name:
    print(fstr(user_input))

输出结果如下:

certi_deep
certi_mahesh
certi_nirbhay

请注意,用户可以在模板中使用表达式,例如:

import os  # assume you have used os somewhere
user_input = r"certi_{os.environ}"

for element in name:
    print(fstr(user_input))

你绝对不想要这个!

因此,一个更安全的选择是定义:

def fstr(template, **kwargs):
    return eval(f"f'{template}'", kwargs)

现在不再允许任意代码运行,但用户仍然可以使用字符串表达式,例如:

user_input = r"certi_{element.upper()*2}"

for element in name:
    print(fstr(user_input, element=element))

输出结果为:

certi_DEEPDEEP
certi_MAHESHMAHESH
certi_NIRBHAYNIRBHAY

这在某些情况下可能是需要的。


8
这是唯一一个真正回答这个问题的答案——如何使用 f-string 格式化字符串并带上变量。 - Zbyněk Winkler
7
这只是一种极其低效的使用 template.format(**kwargs) 的方式。更不用说它是不安全的,因为 eval() 允许用户运行任意表达式。 - Martijn Pieters
@MartijnPieters,您的意思是我上面定义的第二个“safer”函数比f-string本身更不安全吗? - kadee
1
@kadee:是的,因为f-string是静态的,它的表达式在前面确定并且不会改变。使用eval(),你打开了任意表达式的大门,使用户完全访问你的进程和进程具有的访问级别。 - Martijn Pieters
虽然提到了这意味着的安全风险很重要,但这是问题的正确答案。换句话说,我不会将其添加到公共使用代码中,但在从模板(例如HDL的自动测试台生成)自动生成文件时,我发现它非常有用。如果风险是一个问题,解决方法可能是预解析输入字符串,但并非100%无风险。风险与在应用程序中运行未知的Python脚本相同。 - Danilo Ramos
显示剩余2条评论

3
如果您希望用户可以访问您的命名空间,那么您可以这样做,但后果完全由您承担。您可以使用format方法来动态插值,语法非常相似,而不是使用f-strings。
如果您只想让用户访问少量特定变量,您可以像这样做:
name=["deep", "mahesh", "nirbhay"]
user_input = "certi_{element}" # this string i ask from user  

for element in name:
    my_str = user_input.format(element=element)
    print(f"{my_str}")

你当然可以将用户输入的键名与你使用的变量名重命名:
my_str = user_input.format(element=some_other_variable)

你可以直接让用户访问你的整个命名空间(或至少大部分)。请不要这样做,但要意识到你可以这样做:

my_str = user_input.format(**locals(), **globals())

我之所以选择使用print(f'{my_str}')而不是print(my_str),是为了避免文字中的大括号被误认为是进一步的错误扩展。例如:user_input = 'certi_{{{element}}}'

1
为什么假设名称 element任意的 - Martijn Pieters

1

我正在寻找与您的问题类似的东西。 我看到了这个问题的另一个答案:https://dev59.com/clQJ5IYBdhLWcg3wBRA4#54780825

使用那个想法,我修改了你的代码:

user_input = r"certi_"

for element in name:
    print(f"{user_input}{element}")

我得到了这个结果:

certi_deep
certi_mahesh
certi_nirbhay

如果您想保留问题中的布局,则最终编辑将解决问题。
for element in name:
    print(f"{user_input}" "{" f"{element}" "}")

阅读其他问题的安全问题,我认为这种替代方案没有严重的安全风险,因为它没有使用eval()定义新函数。

我不是安全专家,如果我有错误,请纠正我。


-1

这就是你要找的。只需要更改原始代码的最后一行:

name=["deep","mahesh","nirbhay"]
user_input = "certi_{element}" # this string I ask from user  

for element in name:
  print(eval("f'" + f"{user_input}" + "'"))

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