如何postpone/defer 评估f-strings?
How to postpone/defer the evaluation of f-strings?
我正在使用模板字符串来生成一些文件,我喜欢新的 f-strings 的简洁性,用于减少我以前的模板代码,如下所示:
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}
这是完整的 "Ideal 2"。
它不是 f 弦——它甚至不使用 f 弦——但它按要求使用。完全按照指定的语法。没有安全问题,因为我们没有使用 eval()
.
它使用了一点class并实现了由打印自动调用的__str__
。为了摆脱 class 的有限范围,我们使用 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
f 字符串只是一种创建格式化字符串的更简洁的方法,它用 f
替换了 .format(**names)
。如果您不希望以这种方式立即评估字符串,请不要将其设为 f 字符串。将其保存为普通字符串文字,然后在您想要执行插值时调用 format
,就像您一直在做的那样。
当然还有eval
的替代方案。
template.txt
:
f'The current name is {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
调用继续使用常规字符串。
This means the template is a static string with formatting tags in it
是的,这正是我们使用带有替换字段和 .format
的文字的原因,因此我们可以随时通过调用 format
来替换字段。
Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string
这是前缀 f/F
。您可以将它包装在一个函数中并在调用期间推迟评估,但这当然会产生额外的开销:
template_a = lambda: 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
但感觉不对,并且受到以下事实的限制:您只能查看替换中的全局命名空间。尝试在需要本地名称的情况下使用它会惨败,除非作为参数传递给字符串(这完全解决了问题)。
Is there any way to bring in a string and have it interpreted as an f-string to avoid using the .format(**locals())
call?
除了一个函数(包括限制),不,所以最好还是坚持使用 .format
。
或者不使用 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))
使用 .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-strings 的默认 操作,但是现在为时已晚。 F 字符串评估可能会产生大量意外的副作用,并且以延迟的方式发生这种情况会改变程序的执行。
为了使 f 字符串正确延迟,python 需要某种方式来显式切换行为。也许使用字母 'g'? ;)
有人指出,如果字符串转换器中存在错误,延迟日志记录不应崩溃。上面的解决方案也可以做到这一点,将 finally:
更改为 except:
,并在其中粘贴一个 log.exception
。
一个使用f-strings的建议。做你的评价
发生模板的逻辑级别并将其作为生成器传递。
您可以使用 f-strings
在您选择的任何位置展开它
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
将字符串评估为 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
受的启发,下面可以用来定义一个deferred-f-stringclass.
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)
这正是问题所要求的
您想要的似乎被视为 Python enhancement。
同时 - 从链接的讨论来看 - 以下似乎是一个不需要使用 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
怎么样:
s = 'Hi, {foo}!'
s
> 'Hi, {foo}!'
s.format(foo='Bar')
> 'Hi, Bar!'
这些答案中的大多数有时会让您得到一些类似于 f 弦的东西,但在某些情况下它们都会出错。
pypi f-yeah
上有一个包可以完成这一切,只需要额外花费两个字符! (完全公开,我是作者)
from fyeah import f
print(f("""'{'"all" the quotes'}'"""))
f 字符串和格式调用之间有很多差异,这里可能不完整列表
- f-strings 允许任意评估 python 代码
- f-strings 不能在表达式中包含反斜杠(因为格式化字符串没有表达式,所以我想你可以说这没什么区别,但它确实与原始 eval() 不同)可以做到)
- 不得引用格式化字符串中的字典查找。可以引用 f-strings 中的 dict 查找,因此也可以查找非字符串键
- f-strings 具有 format() 没有的调试格式:
f"The argument is {spam=}"
- f-string 表达式不能为空
使用 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
在某些情况下,此示例也会出现变量范围错误。
有很多关于使用 str.format()
的讨论,但如前所述,它不允许使用 f-strings 中允许的大多数表达式,例如算术或切片。使用 eval()
显然也有缺点。
我建议研究一种模板语言,例如 Jinja。对于我的 use-case,它工作得很好。请参阅下面的示例,其中我使用单个花括号覆盖了变量注释语法以匹配 f-string 语法。我没有完全回顾 f-strings 和像这样调用的 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'
我正在使用模板字符串来生成一些文件,我喜欢新的 f-strings 的简洁性,用于减少我以前的模板代码,如下所示:
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}
这是完整的 "Ideal 2"。
它不是 f 弦——它甚至不使用 f 弦——但它按要求使用。完全按照指定的语法。没有安全问题,因为我们没有使用 eval()
.
它使用了一点class并实现了由打印自动调用的__str__
。为了摆脱 class 的有限范围,我们使用 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
f 字符串只是一种创建格式化字符串的更简洁的方法,它用 f
替换了 .format(**names)
。如果您不希望以这种方式立即评估字符串,请不要将其设为 f 字符串。将其保存为普通字符串文字,然后在您想要执行插值时调用 format
,就像您一直在做的那样。
当然还有eval
的替代方案。
template.txt
:
f'The current name is {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
调用继续使用常规字符串。
This means the template is a static string with formatting tags in it
是的,这正是我们使用带有替换字段和 .format
的文字的原因,因此我们可以随时通过调用 format
来替换字段。
Something would have to happen to the string to tell the interpreter to interpret the string as a new f-string
这是前缀 f/F
。您可以将它包装在一个函数中并在调用期间推迟评估,但这当然会产生额外的开销:
template_a = lambda: 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
但感觉不对,并且受到以下事实的限制:您只能查看替换中的全局命名空间。尝试在需要本地名称的情况下使用它会惨败,除非作为参数传递给字符串(这完全解决了问题)。
Is there any way to bring in a string and have it interpreted as an f-string to avoid using the
.format(**locals())
call?
除了一个函数(包括限制),不,所以最好还是坚持使用 .format
。
或者不使用 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))
使用 .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-strings 的默认 操作,但是现在为时已晚。 F 字符串评估可能会产生大量意外的副作用,并且以延迟的方式发生这种情况会改变程序的执行。
为了使 f 字符串正确延迟,python 需要某种方式来显式切换行为。也许使用字母 'g'? ;)
有人指出,如果字符串转换器中存在错误,延迟日志记录不应崩溃。上面的解决方案也可以做到这一点,将 finally:
更改为 except:
,并在其中粘贴一个 log.exception
。
一个使用f-strings的建议。做你的评价 发生模板的逻辑级别并将其作为生成器传递。 您可以使用 f-strings
在您选择的任何位置展开它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
将字符串评估为 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
受
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)
这正是问题所要求的
您想要的似乎被视为 Python enhancement。
同时 - 从链接的讨论来看 - 以下似乎是一个不需要使用 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
怎么样:
s = 'Hi, {foo}!'
s
> 'Hi, {foo}!'
s.format(foo='Bar')
> 'Hi, Bar!'
这些答案中的大多数有时会让您得到一些类似于 f 弦的东西,但在某些情况下它们都会出错。
pypi f-yeah
上有一个包可以完成这一切,只需要额外花费两个字符! (完全公开,我是作者)
from fyeah import f
print(f("""'{'"all" the quotes'}'"""))
f 字符串和格式调用之间有很多差异,这里可能不完整列表
- f-strings 允许任意评估 python 代码
- f-strings 不能在表达式中包含反斜杠(因为格式化字符串没有表达式,所以我想你可以说这没什么区别,但它确实与原始 eval() 不同)可以做到)
- 不得引用格式化字符串中的字典查找。可以引用 f-strings 中的 dict 查找,因此也可以查找非字符串键
- f-strings 具有 format() 没有的调试格式:
f"The argument is {spam=}"
- f-string 表达式不能为空
使用 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
在某些情况下,此示例也会出现变量范围错误。
有很多关于使用 str.format()
的讨论,但如前所述,它不允许使用 f-strings 中允许的大多数表达式,例如算术或切片。使用 eval()
显然也有缺点。
我建议研究一种模板语言,例如 Jinja。对于我的 use-case,它工作得很好。请参阅下面的示例,其中我使用单个花括号覆盖了变量注释语法以匹配 f-string 语法。我没有完全回顾 f-strings 和像这样调用的 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'