如何推迟/推迟 f 字符串的求值?

我使用模板字符串来生成一些文件,我喜欢新的 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}
38563 次浏览

这里有一个完整的“理想2”。

它不是 f 字符串ーー它甚至不使用 f 字符串ーー但它按要求执行。语法与指定的完全一致。没有安全问题,因为我们没有使用 eval()

它使用一个小类并实现了由 print 自动调用的 __str__。为了逃避类的有限作用域,我们使用 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-string 只是创建格式化字符串的一种更简洁的方法,用 f代替 .format(**names)。如果您不希望以这种方式立即对字符串求值,请不要将其设置为 f-string。将它保存为一个普通的字符串文本,然后在以后需要执行插值时对其调用 format,正如您一直在做的那样。

当然,eval也有另一种选择。

返回文章页面

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

但是所有你已经设法做的就是用 eval代替 str.format,这肯定不值得。只要在 format调用中继续使用常规字符串即可。

这意味着模板是一个静态字符串,其中包含格式标记

是的,这就是为什么我们使用替换字段和 .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

或者也许不用 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))

吸毒。格式不是这个问题的正确答案。Python f-string 与 str.format ()模板非常不同... 它们可以包含代码或其他昂贵的操作——因此需要延迟。

下面是一个延迟日志记录器的示例。这将使用 logging.getLogger 的正常前导语,但随后添加新函数,只有在日志级别正确的情况下才解释 f-string。

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-string 计算可能会产生大量意想不到的副作用,如果这种情况以延迟的方式发生,将会改变程序的执行。

为了正确地延迟 f-string,python 需要一些显式切换行为的方法。用字母“ g”怎么样?;)

有人指出,如果字符串转换器中存在 bug,延迟日志记录不应该崩溃。上述解决方案也可以做到这一点,改变 finally:except:,并坚持一个 log.exception在那里。

使用 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

将字符串作为 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

受到 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)

这正是问题所在

您想要的似乎被认为是一个 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

这个怎么样:

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-string 和 format 调用之间有很多不同之处,下面是一个可能不完整的列表

  • F-string 允许对 python 代码进行任意的 eval
  • F-string 不能在表达式中包含反斜杠(因为格式化的字符串没有表达式,所以我想你可以说这没有什么不同,但它确实不同于原始 eval ()所能做的)
  • 格式化字符串中的 dictlookup 不能用引号引用。可以引用 f 字符串中的 dictlookup,因此也可以查找非字符串键
  • F-string 具有 format ()所没有的调试格式: f"The argument is {spam=}"
  • F-string 表达式不能为空

使用 eval 的建议将为您提供完整的 f-string 格式支持,但它们不适用于所有字符串类型。

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 字符串中允许的大多数表达式,比如算术或切片。使用 eval()显然也有它的缺点。

我建议你研究一下像金贾这样的模板语言。对于我的用例,它工作得很好。请参阅下面的示例,其中我用一个花括号覆盖了变量注释语法,以匹配 f-string 语法。我没有完全回顾 f 弦和金贾弦之间的区别。

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'

为此,我更喜欢在 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) )

您可以使用 .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