如何推迟/延迟 f 字符串的评估?

2025-01-09 08:47:00
admin
原创
80
摘要:问题描述:我正在使用模板字符串来生成一些文件,我喜欢新的 f 字符串的简洁性,它可以减少我以前的模板代码,如下所示:template_a = "The current name is {name}" names = ["foo", "bar"] fo...

问题描述:

我正在使用模板字符串来生成一些文件,我喜欢新的 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))
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用