如何推迟/延迟 f 字符串的评估?
- 2025-01-09 08:47:00
- admin 原创
- 80
问题描述:
我正在使用模板字符串来生成一些文件,我喜欢新的 f 字符串的简洁性,它可以减少我以前的模板代码,如下所示:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(**locals()))
现在我可以这样做,直接替换变量:
names = ["foo", "bar"]
for name in names:
print (f"The current name is {name}")
但是,有时将模板定义在其他地方是有意义的 — 在代码中更高位置,或者从文件或其他东西导入。这意味着模板是一个带有格式标记的静态字符串。必须对字符串进行某些操作以告诉解释器将字符串解释为新的 f 字符串,但我不知道是否有这样的事情。
有什么方法可以引入一个字符串并将其解释为 f 字符串以避免使用调用.format(**locals())
?
理想情况下,我希望能够像这样编码......(magic_fstring_function
我不明白的部分在哪里):
template_a = f"The current name is {name}"
# OR [Ideal2] template_a = magic_fstring_function(open('template.txt').read())
names = ["foo", "bar"]
for name in names:
print (template_a)
...获得所需的输出(无需读取文件两次):
The current name is foo
The current name is bar
...但我得到的实际输出是:
The current name is {name}
The current name is {name}
另请参阅:如何将 f-string 与变量一起使用,而不是与字符串文字一起使用?
解决方案 1:
将字符串评估为 f 字符串(具有其全部功能)的简洁方法是使用以下函数:
def fstr(template):
return eval(f'f"""{template}"""')
然后你可以这样做:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print(fstr(template_a))
# The current name is foo
# The current name is bar
而且,与许多其他提出的解决方案相比,您还可以执行以下操作:
template_b = "The current name is {name.upper() * 2}"
for name in names:
print(fstr(template_b))
# The current name is FOOFOO
# The current name is BARBAR
解决方案 2:
这是完整的《理想2》。
它不是 f 字符串——它甚至不使用 f 字符串——但它确实符合要求。语法完全符合规定。由于我们没有使用,因此没有安全问题eval()
。
它使用一个小类并实现__str__
由 print 自动调用的功能。为了摆脱类的有限范围,我们使用inspect
模块跳转到上一帧并查看调用者可以访问的变量。
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
解决方案 3:
这意味着模板是一个带有格式标签的静态字符串
是的,这就是为什么我们拥有带有替换字段和的文字.format
,因此我们可以随时通过调用format
它来替换字段。
必须对字符串进行某些操作,以告诉解释器将该字符串解释为新的 f 字符串
这就是前缀f/F
。您可以将其包装在函数中,并在调用期间推迟评估,但这当然会产生额外的开销:
def template_a():
return f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
print(template_a())
打印结果如下:
The current name is foo
The current name is bar
但感觉不对,而且由于您只能在替换中查看全局命名空间,因此会受到限制。尝试在需要本地名称的情况下使用它将会失败,除非将其作为参数传递给字符串(这完全违背了要点)。
有什么方法可以引入一个字符串并将其解释为 f 字符串以避免使用调用
.format(**locals())
?
除了功能(包括限制)之外,没有,所以不妨坚持使用.format
。
解决方案 4:
怎么样:
s = 'Hi, {foo}!'
s
> 'Hi, {foo}!'
s.format(foo='Bar')
> 'Hi, Bar!'
解决方案 5:
使用 .format 不是这个问题的正确答案。Python f 字符串与 str.format() 模板非常不同……它们可以包含代码或其他昂贵的操作 - 因此需要延迟。
以下是延迟记录器的示例。它使用logging.getLogger的正常前言,但随后添加了仅在日志级别正确时才解释f字符串的新函数。
log = logging.getLogger(__name__)
def __deferred_flog(log, fstr, level, *args):
if log.isEnabledFor(level):
import inspect
frame = inspect.currentframe().f_back.f_back
try:
fstr = 'f"""' + fstr + '"""'
log.log(level, eval(fstr, frame.f_globals, frame.f_locals))
finally:
del frame
log.fdebug = lambda fstr, *args: __deferred_flog(log, fstr, logging.DEBUG, *args)
log.finfo = lambda fstr, *args: __deferred_flog(log, fstr, logging.INFO, *args)
这样做的好处是可以执行以下操作: log.fdebug("{obj.dump()}")
....除非启用了调试,否则无需转储对象。
恕我直言:这应该是f 字符串的默认操作,但现在为时已晚。F 字符串求值可能会产生大量意想不到的副作用,而以延迟的方式发生这种情况会改变程序的执行。
为了使 f 字符串正确延迟,python 需要某种方式明确切换行为。也许使用字母“g”?;)
有人指出,如果字符串转换器中存在错误,延迟日志记录不应崩溃。上述解决方案也可以做到这一点,将 更改为finally:
,except:
然后将 粘贴log.exception
在那里。
解决方案 6:
f 字符串只是创建格式化字符串的更简洁的方法,.format(**names)
用替换f
。如果您不希望以这种方式立即评估字符串,请不要将其设为 f 字符串。将其保存为普通字符串文字,然后format
在您想要执行插值时调用它,就像您一直在做的那样。
当然,还有另一种选择eval
。
template.txt
:
f'当前名称是{name}'
代码:
>>> template_a = open('template.txt').read()
>>> names = 'foo', 'bar'
>>> for name in names:
... print(eval(template_a))
...
The current name is foo
The current name is bar
但您所做的只是str.format
用替换eval
,这肯定不值得。只需继续使用带有调用的常规字符串即可format
。
解决方案 7:
您想要的似乎被视为 Python增强。
同时 - 从链接的讨论来看 - 以下似乎是一个合理的解决方法,不需要使用eval()
:
class FL:
def __init__(self, func):
self.func = func
def __str__(self):
return self.func()
template_a = FL(lambda: f"The current name, number is {name!r}, {number+1}")
names = "foo", "bar"
numbers = 40, 41
for name, number in zip(names, numbers):
print(template_a)
输出:
The current name, number is 'foo', 41
The current name, number is 'bar', 42
解决方案 8:
或者也许不使用 f 字符串,只需格式化:
fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
print(fun(name=name))
未命名版本:
fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
print(fun(name))
解决方案 9:
受到kadee 的回答的启发,以下内容可用于定义延迟 f 字符串类。
class FStr:
def __init__(self, s):
self._s = s
def __repr__(self):
return eval(f"f'{self._s}'")
...
template_a = FStr('The current name is {name}')
names = ["foo", "bar"]
for name in names:
print (template_a)
这正是问题所要求的
解决方案 10:
大多数答案都会让你得到一些行为类似于 f 字符串的东西,但在某些情况下它们都会出错。pypi 上有一个包f-yeah
可以完成所有这些操作,只需要多花两个字符!(完全公开,我是作者)
from fyeah import f
print(f("""'{'"all" the quotes'}'"""))
f 字符串和格式调用之间有很多区别,下面是一个可能不完整的列表
f 字符串允许对 Python 代码进行任意求值
f 字符串不能在表达式中包含反斜杠(因为格式化的字符串没有表达式,所以我想您可能会说这没有区别,但它确实与原始 eval() 所能做的事情不同)
格式化字符串中的字典查找不能用引号引起来。f 字符串中的字典查找可以用引号引起来,因此也可以查找非字符串键
f 字符串具有 format() 没有的调试格式:
f"The argument is {spam=}"
f 字符串表达式不能为空
使用 eval 的建议将为您提供完整的 f 字符串格式支持,但它们并不适用于所有字符串类型。
def f_template(the_string):
return eval(f"f'{the_string}'")
print(f_template('some "quoted" string'))
print(f_template("some 'quoted' string"))
some "quoted" string
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f_template
File "<string>", line 1
f'some 'quoted' string'
^
SyntaxError: invalid syntax
此示例在某些情况下也会导致变量作用域错误。
解决方案 11:
为此,我更喜欢在 lambda 函数中使用 fstring,例如:
s = lambda x: f'this is your string template to embed {x} in it.'
n = ['a' , 'b' , 'c']
for i in n:
print( s(i) )
解决方案 12:
关于使用 有很多讨论str.format()
,但如前所述,它不允许 f 字符串中允许的大多数表达式,例如算术或切片。使用eval()
显然也有其缺点。
我建议研究一下模板语言,比如 Jinja。就我的用例而言,它工作得很好。请参见下面的示例,其中我用一个花括号覆盖了变量注释语法以匹配 f 字符串语法。我没有完全回顾 f 字符串和像这样调用的 Jinja 之间的区别。
from jinja2 import Environment, BaseLoader
a, b, c = 1, 2, "345"
templ = "{a or b}{c[1:]}"
env = Environment(loader=BaseLoader, variable_start_string="{", variable_end_string="}")
env.from_string(templ).render(**locals())
结果是
'145'
解决方案 13:
我发现这个问题非常有趣,并编写了自己的库来实现惰性 f 字符串。
安装:
pip install fazy
并使用:
import f
number = 33
print(f('{number} kittens drink milk'))
例如,此解决方案非常适合用于日志记录。请通过链接在文档中了解有关功能和限制的更多信息。
解决方案 14:
使用 f 字符串的建议。在模板发生的逻辑层面上进行评估并将其作为生成器传递。您可以使用 f 字符串在您选择的任何时间点解开它
In [46]: names = (i for i in ('The CIO, Reed', 'The homeless guy, Arnot', 'The security guard Spencer'))
In [47]: po = (f'Strangely, {next(names)} has a nice {i}' for i in (" nice house", " fast car", " big boat"))
In [48]: while True:
...: try:
...: print(next(po))
...: except StopIteration:
...: break
...:
Strangely, The CIO, Reed has a nice nice house
Strangely, The homeless guy, Arnot has a nice fast car
Strangely, The security guard Spencer has a nice big boat
解决方案 15:
您可以使用.format
样式替换并明确定义被替换的变量名称:
template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
print (template_a.format(name=name))
输出
The current name is foo
The current name is bar
解决方案 16:
我遇到了这个问题,它与我尝试做的事情类似,唯一的区别是我需要尽早评估 f 字符串的一些变量,然后推迟其他 f 字符串变量以供稍后确定。
借用原来的例子,下面是我实现的方法:
from datetime import date
now = date.today()
names = ["foo", "bar"]
# double bracket allows `name` to be evaluated by the next format() call
template_a = f"The current name is {{name}} and the current date is {now}."
for name in names:
print (template_a.format(name=name))